常见配置
不同粒度配置的覆盖关系
- ⽅法级优先,接⼝级次之,全局配置再次之。
- 如果级别⼀样,则消费⽅优先,提供⽅次之。
- 启动检查
<dubbo:reference interface="com.foo.BarService" check="false" />
- 超时配置
<dubbo:reference interface="com.foo.BarService" check="false" timeout="1000"/>
- 重试次数
幂等(设置重试次数,查询、删除、修改**) ⾮幂等(不能设置,新增)
<dubbo:reference id="orderService" interface="com.end.dubbo.api.service.OrderService" retries="3" />
- 多版本
<dubbo:reference id="orderService" interface="com.end.dubbo.api.service.OrderService" version="2.0.0" />
- 分组
<dubbo:reference id="orderService" interface="com.end.dubbo.api.service.OrderService" group="g1" />
api与spi
- API Application Programming Interface
⼤多数情况下,都是 实现⽅ 来制定接⼝并完成对接⼝的不同实现, 调⽤⽅ 仅仅依赖却⽆权选择不同
实现。 - SPI Service Provider Interface
⽽如果是 调⽤⽅ 来制定接⼝, 实现⽅ 来针对接⼝来实现不同的实现。 调⽤⽅ 来选择⾃⼰需要的 实现
⽅ 。
JDK中的spi
SPI 全称为 (Service Provider Interface) ,是JDK内置的⼀种服务提供发现机制。 ⽬前有不少框架⽤它来做服务的扩展发现, 简单来说,它就是⼀种动态替换发现的机制, 举个例⼦来说, 有个接⼝,想运⾏时动态的给它添加实现,你只需要添加⼀个实现,通过⼀个简单例⼦来说明SPI是如何使⽤的。 ⾸先通过⼀张图来看看,⽤SPI需要遵循哪些规范,因为spi毕竟是JDK的⼀种标准。
public interface Animal {
void say();
}
public class Cat implements Animal {
@Override
public void say() {
System.out.println("cat saying");
}
}
配置META-INF类
- 原理
在ServiceLoader的load⽅法中⾸先会获取上下⽂类加载器,然后构造⼀个ServiceLoader,在ServiceLoader中有⼀个懒加载器,懒加载器会通过BufferedReader来从META-INF/services路径下读取对应的接⼝名的全路径名⽂件,也就是我们配置的⽂件,然后通过⽂件的类解析器读取⽂件中的内容,再通过类加载器加载类的全路径。
缺点:
- ⽆法按需加载。虽然 ServiceLoader 做了延迟载⼊,使⽤了LazyIterator,但是基本只能通过遍历全部获取,接⼝的实现类得全部载⼊并实例化⼀遍。如果你并不想⽤某些实现类,或者某些类实例化很耗时,它也被载⼊并实例化了,假如我只需要其中⼀个,其它的不需要这就形成
了⼀定的资源消耗浪费; - 不具有IOC的功能,假如我有⼀个实现类,如何将它注⼊到我的容器中呢,类之间依赖关系如何完成呢?
- serviceLoader不是线程安全的,会出现线程安全的问题。
dubbo中的spi
@SPI
public interface Person {
void say();
}
public class Man implements Person {
@Override
public void say() {
System.out.println("man saying");
}
}
配置:
man=dubbo.impl.Man
woman=dubbo.impl.Woman
线程派发模型
- 配置
<dubbo:protocol name="dubbo" port="20880" threadpool="fixed" threads="200" iothreads="8" accepts="0" queues="100" dispatcher="all"/>
线程派发策略
有5种派发策略:
- 默认是all:所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,⼼跳等。 即worker线程接收到事件后,将该事件提交到业务线程池中,⾃⼰再去处理其他事。
- direct:worker线程接收到事件后,由worker执⾏到底(所有消息都不派发到线程池,全部在Io线程上直接执⾏)。
- message:只有请求响应消息派发到线程池,其它连接断开事件,⼼跳等消息,直接在 IO线程上执⾏
- execution:只请求消息派发到线程池,不含响应(客户端线程池),响应和其它连接断开事件,⼼跳等消息,直接在 IO 线程上执⾏
- connection:在 IO 线程上,将连接断开事件放⼊队列,有序逐个执⾏,其它消息派发到线程池。
以及四个常⽤的threadpool:
- fixed 固定⼤⼩线程池,启动时建⽴线程,不关闭,⼀直持有。
- cached 缓存线程池,空闲⼀分钟⾃动删除,需要时重建。
- limited 可伸缩线程池,但池中的线程数只会增⻓不会收缩。只增⻓不收缩的⽬的是为了避免收缩时突然来了⼤流量引起的性能问题。
- eager 优先创建Worker线程池。在任务数量⼤于corePoolSize但是⼩于maximumPoolSize时,优先创建Worker来处理任务。当任务数量⼤于maximumPoolSize时,将任务放⼊阻塞队列中。阻塞队列充满时抛出RejectedExecutionException。(相⽐于cached:cached在任务数量超过maximumPoolSize时直接抛出异常⽽不是将任务放⼊阻塞队列)
Dubbo 线程模型图
整体步骤:(受限于派发策略,以默认的all为例)
- 客户端的主线程发出⼀个请求后获得future,在执⾏get时进⾏阻塞等待;
- 服务端使⽤worker线程(netty通信模型)接收到请求后,将请求提交到server线程池中进⾏处理。
- server线程处理完成之后,将相应结果返回给客户端的worker线程池(netty通信模型),最后,worker线程将响应结果提交到client线程池进⾏处理。
- client线程将响应结果填充到future中,然后唤醒等待的主线程,主线程获取结果,返回给客户端。