默认使用dubbo协议,hessian序列化框架,zookepper注册中心,使用Neety通信,随机负载均衡,失败自动切换,自动重试其它服务器,默认同步阻塞
Consumer:服务消费者,即服务调用方;
Provider:服务提供者,即被调用方;
Registry:注册中心,服务注册与服务发现,dubbo支持4种注册中心(multicast zookeeper redis simple),dubbo推荐使用zookeeper注册中心;
Monitor:服务监控中心,提供服务调用次数和调用时间统计;
Container:服务运行容器;
分为十层
代理层的动态代理使用的是javaSsesit
random loadBalance
默认情况下,使用随机调用来实现负载均衡,可以对不同provider。也可以使用不同权重,来进行负载均衡
randomRobin loadBalance
轮询来分发流量,每台机器的次数严格一样。也可以调整权重
leastactive loadBalance
自动感知,如果某个机器性能越差,分发的请求就越少
一致性hash
如果需要一类请求都到一个节点,就可以使用,配合缓存很好用
默认的协议是dubbo,适用于小数据量大并发,单一长连接,NIO异步传输(TCP传输),Hessian 二进制序列化
Dubbo 缺省协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。
反之,Dubbo 缺省协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。
特性
缺省协议,使用基于 mina 1.1.7 和 hessian 3.2.1 的 tbremoting 交互。
连接个数:单连接
连接方式:长连接
传输协议:TCP
传输方式:NIO 异步传输
序列化:Hessian 二进制序列化
适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用 dubbo 协议传输大文件或超大字符串。
适用场景:常规远程服务方法调用
全量配置
<dubbo:protocol name=“dubbo” port=“9090” server=“netty” client=“netty” codec=“dubbo” serialization=“hessian2” charset=“UTF-8” threadpool=“fixed” threads=“100” queues=“0” iothreads=“9” buffer=“8192” accepts=“1000” payload=“8388608” />
//简单配置,使用dubbo协议,端口20868,线程数200
<dubbo:protocol name="dubbo" port="20868" threadpool="cached" threads="200"/>
阻塞式短连接,同步TCP传输,Java 标准二进制序列化
多个短连接,HTTP同步传输,Hessian二进制序列化
在服务提供方或服务消费方中,均可设置
<dubbo:reference id="demoService" interface="org.apache.dubbo.demo.DemoService" cluster="failover" retries="1"/>
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" cluster="failover" retries="2" />
failOver cluster模式
失败自动切换,自动重试。
fialFast cluster模式
一次调用失败就立即失败,不在重试,适用于写操作
failSafe cluster模式
出现异常忽略掉,常用语记录日志
failBack
失败了后台自动记录请求,然后定时重发,比较适合于写消息队列
forking
并行的调用多个provider,只要一个成功就立即返回
broadcast
依次调用所有的provider
duboo在启动时候会进行所有消费者的服务检查,如果服务都不可用,会抛错,默认为true,通过false进行关闭
mock只在出现非业务异常(比如超时,网络异常等)时执行,可配置bool值,或null
<dubbo:reference id="xxxService" interface="com.x..service.xxxxService" check="false" mock="return null" />
现在消费的标签中,写mock=true
<dubbo:reference id="xxxService" interface="com.x..service.xxxxService" check="false" mock="true" />
然后服务提供方在目录下创建mock的处理类,dubbo在调用失败的时候会自动的访问mockxxxxService来进行执行
class xxxxServiceMock implements xxxxService{
//注意类名必须是接口名+Mock
fun1;
fun2;
}
可以通过启动时的健康检查来暴露(依赖的双方启动时候必有先后)
在 Provider 上可以配置的 Consumer 端的属性有:
脚本路由
条件路由,需要在管理控制台中输入路由规则
# DemoService的sayHello方法只能消费所有端口为20880的服务实例
# DemoService的sayHi方法只能消费所有端口为20881的服务实例
scope: service
force: true
runtime: true
enabled: true
key: org.apache.dubbo.samples.governance.api.DemoService
conditions:
- method=sayHello => address=*:20880
- method=sayHi => address=*:20881
...
# app1的消费者只能消费所有端口为20880的服务实例
# app2的消费者只能消费所有端口为20881的服务实例
---
scope: application
force: true
runtime: true
enabled: true
key: governance-conditionrouter-consumer
conditions:
- application=app1 => address=*:20880
- application=app2 => address=*:20881
...
scope
表示路由规则的作用粒度,scope的取值会决定key的取值。必填
Key
明确规则体作用在哪个服务或应用。必填
enabled=true
当前路由规则是否生效,可不填,缺省生效。
force=false
当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 false
。
runtime=false
是否在每次调用时执行路由规则,否则只在提供者地址列表变更时预先执行并缓存结果,调用时直接从缓存中获取路由结果。如果用了参数路由,必须设为 true
,需要注意设置会影响调用的性能,可不填,缺省为 false
。
priority=1
路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0
。
conditions
定义具体的路由规则内容。必填。
同步调用是一种阻塞式的调用方式,即 Consumer 端代码一直阻塞等待,直到 Provider 端返回为止;
通常,一个典型的同步调用过程如下:
阻塞
状态;Dubbo底层Consumer发起调用后返回一个Future对象,Future通过.get(Timeout)来进行阻塞,并在超时时间以后,业务线程将会异常返回; 在超时时间前返回,会唤醒阻塞的业务线程
consumer端对应的服务设置 async=“true”。
使用案例,Dubbo的异步调用
AsyncService service = ...;
String result = service.goodbye("samples");// 这里的返回值为空,请不要使用
Future<String> future = RpcContext.getContext().getFuture();
... // 业务线程可以开始做其他事情
result = future.get(); // 阻塞需要获取异步结果时,也可以使用 get(timeout, unit) 设置超时时间
使用案例,服务端返回异步对象
基于java 8中引入的CompletableFuture,dubbo在2.7.0版本中也增加了对CompletableFuture的支持,我们可以直接定义一个返回CompletableFuture类型的接口。
public interface AsyncService {
String sayHello(String name);
CompletableFuture<String> sayHelloAsync(String name);
}
//具体实现:
public class AsyncServiceImpl implements AsyncService {
@Override
public String sayHello(String name) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return name;
}
@Override
public CompletableFuture<String> sayHelloAsync(String name) {
return CompletableFuture.supplyAsync(() -> name);
}
}
//消费端调用:
CompletableFuture<String> completableFuture = asyncService.sayHelloAsync("async");
String result = completableFuture.get();
注意:
异步调用具有传递性,B同步调用C,但是当A异步调用B,就会导致B异步调用C
dubbo允许consumer 端在调用之前、调用之后或出现异常时,触发 oninvoke、onreturn、onthrow 三个事件。类似于Spring中的前置增强、后置增强和异常抛出增强。
只需要在服务引用时,增加以下配置指定事件通知的方法即可。
//配置
<dubbo:reference id="asyncService" check="false" interface="com.alibaba.dubbo.demo.AsyncService" url="localhost:20880">
<dubbo:method name="sayHello"
oninvoke="notifyServiceImpl.onInvoke"
onreturn="notifyServiceImpl.onReturn"
onthrow="notifyServiceImpl.onThrow" />
</dubbo:reference>
//事件通知服务
public class NotifyServiceImpl implements NotifyService {
// 方法参数与调用方法参数相同
@Override
public void onInvoke(String name) {
System.out.println("onInvoke: " + name);
}
// 第一个参数为调用方法的返回值,其余为调用方法的参数
@Override
public void onReturn(String retName, String name) {
System.out.println("onReturn: " + name);
}
// 第一个参数为调用异常,其余为调用方法的参数
@Override
public void onThrow(Throwable ex, String name) {
System.out.println("onThrow: " + name);
}
}
优雅下线的要求是:
服务消费者不应该请求到已经下线的服务提供者
在途请求需要处理完毕,不能被停机指令中断
Dubbo的优雅下线:
等待进程关闭的指令,利用ShutdownHook
先将所有的已经提供的服务和订阅的服务从列表中移除(对于自己是消费者来说,提供者的变更不会再通知到自己), 然后关闭所有使用的注册中心(比如断开ZK的连接)
等待10S,等消费者来得及反应
关闭所有的协议(先关闭服务提供者、在关闭服务消费者),需要等待所有正在执行的协议执行完成(应对上面第二点)
Invoker
和Exporter
;关闭JVM
具体的是Spring启动完成以后会发一个ContextRefreshedEvent事件,Dubbo的ServiceBean实现ApplicationListener,监听这个事件做处理
部分参考实战
dubbo是基于java的spi机制进行扩展。
//在java的spi机制中,通过ServiceLoader来读取META-INF/services文件中的内容,并根据内容生成对应类,案例如下:
//1.定义一个接口IRepository用于实现数据储存,成为扩展点
public interface IRepository {
void save(String data);
}
//2.提供IRepository的实现 IRepository有两个实现,称为扩展。MysqlRepository和MongoRepository。
public class MysqlRepository implements IRepository{
public void save(String data) {
System.out.println("Save " + data + " to Mysql");
}
}
public class MongoRepository implements IRepository {
public void save(String data) {
System.out.println("Save " + data + " to Mongo");
}
}
//3 添加配置文件 在META-INF/services目录添加一个文件,文件名和接口全名称相同,所以文件是META-INF/services/com.demo.IRepository。文件内容为:
com.demo.MongoRepository
com.demo.MysqlRepository
//4 利用ServiceLoader来加载IRepository的实现:
ServiceLoader<IRepository> serviceLoader = ServiceLoader.load(IRepository.class);
Iterator<IRepository> it = serviceLoader.iterator();
while (it != null && it.hasNext()){
IRepository demoService = it.next();
System.out.println("class:" + demoService.getClass().getName());
demoService.save("tom");
}
在上面这个案例中,利用ServiceLoader来读取文件,并根据文件生成对应的类实例
扩展点
java接口
扩展
java接口实现类
扩展实例
java接口实现类的实例
扩展自适应实例
java接口实现类的代理类实例
在Dubbo中:
@Spi标示接口表示这个接口是一个扩展点,可以被Dubbo的ExtentionLoader加载。
@Adaptive标示接口的方法,表示该方法是一个自适应方法,Dubbo在为扩展点生成自适应实例时,如果方法有@Adaptive注解,会为该方法生成对应的代码。方法内部会根据方法的参数,来决定使用哪个扩展。 @Adaptive注解用在类上代表实现一个装饰类,类似于设计模式中的装饰模式,它主要作用是返回指定类,目前在整个系统中AdaptiveCompiler、AdaptiveExtensionFactory这两个类拥有该注解。
extentionLoader,Dubbo使用这个,ExtensionLoader.class.getClassLoader();
来获得ClassLoader去加载对应目录的文件
扩展别名,在对应目录的文件中,和Java SPI不同,Dubbo中的扩展都有一个别名,用于在应用中引用它们。
random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
加载目录:
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
dubbo会加载这几个文件目录下的配置文件
案例如下完整代码
//1.定义LoadBalance接口,实现LoadBalance,在LoadBalance中已经用@Spi标示了接口,用@Adaptive("loadbalance")标示了select方法
package com.dubbo.spi.demo.consumer;
public class DemoLoadBalance implements LoadBalance {
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
System.out.println("DemoLoadBalance: Select the first invoker...");
return invokers.get(0);
}
}
//2.添加配置文件:META-INF/dubbo/com.alibaba.dubbo.rpc.cluster.LoadBalance
demo=com.dubbo.spi.demo.consumer.DemoLoadBalance
//3. 需要显式的告诉Dubbo使用demo的负载均衡进行消费。如果是通过spring的方式使用Dubbo,可以在xml文件中进行设置。
<dubbo:reference id="helloService" interface="com.dubbo.spi.demo.api.IHelloService" loadbalance="demo" />
//启动
部分参考源码
主要类,不继承或实现其它类或接口
主要方法:
LoadBalance lb = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName)
RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getAdaptiveExtension()
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
// 扩展点必须是接口
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
// 必须要有@SPI注解
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type without @SPI Annotation!");
}
// 从缓存中根据接口获取对应的ExtensionLoader
// 每个扩展只会被加载一次
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
// 初始化扩展
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
public T getExtension(String name) {
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
// 从缓存中获取,如果不存在就创建
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
createExtension
private T createExtension(String name) {
// 根据扩展点名称得到扩展类,比如对于LoadBalance,根据random得到RandomLoadBalance类
Class<?> clazz = getExtensionClasses().get(name);
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
// 使用反射调用nesInstance来创建扩展类的一个示例
EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 对扩展类示例进行依赖注入
injectExtension(instance);
// 如果有wrapper,添加wrapper
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
}
createExtension方法做了以下事情:
先根据name来得到对应的扩展类。从ClassPath下META-INF
文件夹下读取扩展点配置文件。
使用反射创建一个扩展类的实例
对扩展类实例的属性进行依赖注入,即IOC。
如果有wrapper,添加wrapper。即AOP,与上一步一起组合成了dubbo的自动装配。
四步完成,即创建并初始化了一个扩展实例。这个实例的依赖被注入了,也根据需要被包装了。到此为止,这个扩展实例就可以被使用了。
根据name得到对应的扩展类,也利用了缓存,如果没有则从路径中寻找
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
使用反射获取对应类的实例,创建的扩展实例的属性都是空值。
依赖注入
private T injectExtension(T instance) {
for (Method method : instance.getClass().getMethods()) {
//判断是否是set方法来判断是否是属性的设置方法
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
Class<?> pt = method.getParameterTypes()[0];
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
}
}
return instance;
}
以下要点:
Dubbo的方案是查找Java标准的setter方法。即方法名以set开始,只有一个参数。如果扩展类中有这样的set方法,Dubbo会对其进行依赖注入,类似于Spring的set方法注入。
因为注入的对象可能包括Dubbo的spi扩展,也可能包括Spring的Bean,所以使用Object object = objectFactory.getExtension(pt, property)
来实现,objectFactory对象是在构造函数中确定的。
ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension())
而ExtensionFactory有三个实现类(真正最后生效的还是1和2):
SpiExtensionFactory:Dubbo自己的Spi去加载Extension
SpringExtensionFactory:从Spring容器中去加载Extension,加载了ApplicationContext
AdaptiveExtensionFactory: 自适应的AdaptiveExtensionLoader
//有@Adaptive注解。前面提到了,Dubbo会为每一个扩展创建一个自适应实例
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
private final List<ExtensionFactory> factories;
public AdaptiveExtensionFactory() {
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
for (String name : loader.getSupportedExtensions()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
}
public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
}
}
AdaptiveExtensionLoader类有@Adaptive注解。前面提到了,Dubbo会为每一个扩展创建一个自适应实例。如果扩展类上有@Adaptive,会使用该类作为自适应类。如果没有,Dubbo会为我们创建一个。
所以ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension())
会返回一个AdaptiveExtensionLoader实例,作为自适应扩展实例。
AdaptiveExtensionLoader会遍历所有的ExtensionFactory实现,尝试着去加载扩展。如果找到了,返回。如果没有,在下一个ExtensionFactory中继续找。
Dubbo内置了两个ExtensionFactory,分别从Dubbo自身的扩展机制和Spring容器中去寻找。由于ExtensionFactory本身也是一个扩展点,我们可以实现自己的ExtensionFactory,让Dubbo的自动装配支持我们自定义的组件。比如,我们在项目中使用了Google的guice这个 IOC 容器。我们可以实现自己的GuiceExtensionFactory,让Dubbo支持从guice容器中加载扩展。
AOP
dubbo的扩展机制支持Aop,在Dubbo中有一种特殊的类是Wrapper类,通过装饰器模式,使用包装类包装原始的扩展点实例。在原始扩展点实现前后插入其他逻辑,实现AOP功能。
class A{
private A a;
public A(A a){//通过构造函数来设置属性
this.a = a;
}
}
Dubbo中这样的Wrapper类有ProtocolFilterWrapper, ProtocolListenerWrapper等,分别在dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol路径的配置文件中进行配置:
filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper mock=org.apache.dubbo.rpc.support.MockProtocol
加载配置文件:
try {
clazz.getConstructor(type); //通过clazz.getConstructor(type)来获取,参数是扩展点接口的构造函数,注意构造函数的参数类型是扩展点接口,而不是扩展类。
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} catch (NoSuchMethodException e) {}
//以ProtocolFilterWrapper为例,配置文件对应的name是filter
public class ProtocolFilterWrapper implements Protocol {
private final Protocol protocol;
// 有一个参数是Protocol的复制构造函数
public ProtocolFilterWrapper(Protocol protocol) {
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
}
扩展点自适应其实就是一个扩展点的代理。Dubbo中每一个扩展点都有一个自适应类,如果没有显式提供,Dubbo会自动为我们创建一个,默认使用Javaassist。
//自适应扩展类的代码
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
}
}
}
return (T) instance;
}
// 核心方法createAdaptiveExtension内容
private T createAdaptiveExtension() {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
}
//getAdaptiveExtensionClass内容
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
//createAdaptiveExtensionClass内容
private Class<?> createAdaptiveExtensionClass() {
//生成自适应类的Java源码,使用一个StringBuilder来构建自适应类的Java源码。
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
//再将源码编译成Java的字节码,加载到JVM中。
return compiler.compile(code, classLoader);
}
//compiler的内容,使用javassist:
@SPI("javassist")
public interface Compiler {
Class<?> compile(String code, ClassLoader classLoader);
}
参考: Dubbo用户手册添加链接描述。