1、dubbo为我们提供了很多功能,如服务的集群容错、服务的负载均衡、服务降级、服务的多协议、多注册中心(可不同协议),服务的分组、服务的多版本等,功能比较丰富。
2、dubbo 的启动时检查
2.1、接口级别的启动检查:
@DubboReference(check = false)
private ISayHelloService sayHelloService;
这种方式就表示,在dubbo应用启动阶段,如果ISayHelloService接口没有提供者的话,dubbo应用也会启动成功,如果check属性设置为true,那就表示如果启动阶段找不到提供者,那么应用就会启动失败。
总结:接口级别配置check:
true :表示无提供者应用启动会失败。
false:表示无提供者应用启动会成功。
默认是true。
2.2、应用级别的启动检查:
@DubboReference
private ISayHelloService sayHelloService;
properties中配置:
dubbo.consumer.check=false
这种配置方式表示整个应用中的所有接口都关闭启动检查,如果我们显示的配置@DubboReference(check = true)那么这个接口将还是需要检查,也就是说应用级别的配置dubbo.consumer.check=false只会针对我们没有显示配置的接口。
2.3、启动时注册中心的检查
dubbo.registry.check=false 表示如果注册订阅失败时,也允许启动,默认为true表示注册订阅失败时候应用启动失败。
3、集群容错
3.1、集群容错-> failover 失败重试其他机器
failover 是默认的集群容错策略,且默认是重试2次,也就是重试2台其他机器,因此这个策略尽量不要使用在非事务环境下的数据持久化业务接口,可以使用在读操作的场景。
演示:我们制造一个超市异常出来测试,注意:失败的概念是指dubbo内部机制失败,自己业务抛出的异常不叫失败。
@DubboService
public class SayHelloService implements ISayHelloService {
@Override
public String sayyHello() throws InterruptedException {
System.out.println("call sayyHello. . .");
Thread.sleep(100000);
return "hello...";
}
}
这个提供者我们复制3个服务出来:
三个提供者,一个消费者
消费者代码:
@Service
public class SayHelloService {
//retries = 2默认就是两次,默认的的集群容错策略是failover
@DubboReference(check = false, retries = 2,timeout = 1000)
private ISayHelloService sayHelloService;
public String say() throws InterruptedException {
return sayHelloService.sayyHello();
}
}
测试代码:
@RestController
public class SayHelloController {
@Autowired
private SayHelloService sayHelloService;
@GetMapping("say")
public String say() throws InterruptedException {
return sayHelloService.say();
}
}
浏览器输入:http://localhost:8081/say 注意:只请求一次。
结果展示:
消费者超时异常:
ProviderApplication控制台:
provider2控制台:
provider3控制台:
3.2、集群容错->快速失败failfast
failfast快速失败的含义就是一旦调用一台机器失败后,就直接返回错误,怒不会重试了,这种使用在有事务的数据持久化的业务场景。注意dubbo 不保证幂等性,幂等性需要自己的接口保证。
消费者代码:
@Service
public class SayHelloService {
//配置集群容错为快速失败 failfast
@DubboReference(check = false, cluster = "failfast", timeout = 1000)
private ISayHelloService sayHelloService;
public String say() throws InterruptedException {
return sayHelloService.sayyHello();
}
}
提供者代码不变还是上面的代码,测试方式也不变。
测试结果展示:
消费者超时异常:
ProviderApplication控制台:
provider2控制台:
provider3控制台:
3.3、集群容错->失败安全failsafe:
失败安全failsafe的含义就是出现失败时,直接忽略,如果方法有返回值就会直接返回null。
测试代码:提供者代码还是不变,测试方式也不变。
@Service
public class SayHelloService {
//配置集群容错为失败安全 failsafe
@DubboReference(check = false, cluster = "failsafe", timeout = 1000)
private ISayHelloService sayHelloService;
public String say() throws InterruptedException {
String s = sayHelloService.sayyHello();
return s;
}
}
测试结果展示:
3.4、集群容错->failback 失败自动恢复
failback 失败自动恢复的含义就是一旦失败,后台记录失败请求,定时重发,重发的规则会尝试调用其他机器,默认只最多只会重发3次。类似于MQ消费失败后定时重新投递。通常用于消息通知操作。
使用方式:消费者代码
@Service
public class SayHelloService {
//配置集群容错为失败自动恢复 failback
@DubboReference(check = false, cluster = "failback", timeout = 1000)
private ISayHelloService sayHelloService;
public String say() throws InterruptedException {
String s = sayHelloService.sayyHello();
return s;
}
}
3.5、集群容错->并行调用forking
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通dubbo.consumer.forks=2来设置最大并行数。
使用代码:消费者代码
@Service
public class SayHelloService {
//配置集群容错为并行调用 forking
@DubboReference(check = false, cluster = "forking",timeout = 1000)
private ISayHelloService sayHelloService;
public String say() throws InterruptedException {
String s = sayHelloService.sayyHello();
return s;
}
}
3.6、集群容错->广播调用所有服务broadcast
广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息。
使用代码:消费者代码
@Service
public class SayHelloService {
//配置集群容错为广播调用broadcast
@DubboReference(check = false, cluster = "broadcast",timeout = 1000)
private ISayHelloService sayHelloService;
public String say() throws InterruptedException {
String s = sayHelloService.sayyHello();
return s;
}
}
4、负载均衡配置
4.1、配置形式
@DubboReference(check = false, loadbalance = "roundrobin", timeout = 1000) loadbalance参数为配置负载均衡策略。
4.2、可配置负载均衡策略
random:随机策略,按权重设置随机概率。
roundRobin:轮询策略。
leastActive:最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
consistentHash: 一致性 Hash,相同参数的请求总是发到同一提供者,当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者。
5、dubbo提供者线程模型
5.1、配置方式:
dubbo.protocol.dispatcher=message 派发策略
dubbo.protocol.threadpool=fixed 线程池类型
dubbo.protocol.threads=300 线程数量
5.2、可选选项:
Dispatcher
all:
所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
direct:
所有消息都不派发到线程池,全部在 IO 线程上直接执行。
message:
只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
execution:
只有请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
connection:
在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。
ThreadPool
fixed:
固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)
cached:
缓存线程池,空闲一分钟自动删除,需要时重建。
limited:
可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。
eager:
优先创建Worker
线程池。在任务数量大于corePoolSize
但是小于maximumPoolSize
时,优先创建Worker
来处理任务。当任务数量大于maximumPoolSize
时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException
。(相比于cached
:cached
在任务数量超过maximumPoolSize
时直接抛出异常而不是将任务放入阻塞队列)。
6、dubbo 直连
直连的含义就是不走注册中心,直接使用提供者地址进行调用。
配置形式:@DubboReference(url = "dubbo://localhost:20880") 使用url来直连服务进行调用。
7、dubbo只订阅
只订阅的含义就是当前应用只会消费所需接口,就算有提供接口,也不会注册到注册中心。
配置形式:默认值都是true,false 表示禁止注册
dubbo.provider.register=false
dubbo.registry.register=false
dubbo.protocol.register=false
上面三个配置都会禁止注册当前应用的所有接口到注册中心,即使我们在接口上显示@DubboService(register = true)
也不会注册接口到注册中心。
8、dubbo只注册
只注册的含义就是,当前应用只会将自己提供的服务注册到注册中心,如果有需要消费服务的地方都会被禁止消费服务。
配置形式:默认值
dubbo.registry.subscribe=false
9、静态服务
有时候希望人工管理服务提供者的上线和下线,此时需将注册中心标识为非动态管理模式。
配置形式:false表示需要手动启用。
dubbo.registry.dynamic=false 整个应用的所有接口都静态
@DubboService(dynamic = false) 单个接口静态
10、多协议
dubbo 默认是发布dubbo协议,但是我们可以定义发布多协议,如下案例:
application.properties配置:
发布一个dubbo协议
dubbo.protocols.dubbo.name=dubbo
dubbo.protocols.dubbo.port=20880
发布一个rest协议
dubbo.protocols.rest.name=rest
dubbo.protocols.rest.port=9090
dubbo.protocols..rest.server=jeety
rest协议需要的4个依赖包,restweb服务器我们使用jetty:
<!--dubbo rest 协议 server 使用jeety 的依赖 start-->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<version>3.13.0.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<version>3.13.0.Final</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.4.19.v20190610</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>9.4.19.v20190610</version>
</dependency>
<!--dubbo rest 协议 server 使用jeety 的依赖 end-->
接口定义做修改:
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@Path("/say")
public interface ISayHelloService {
@GET
@Path("/sayHello")
String sayyHello() throws InterruptedException;
}
服务实现发布修改:
@DubboService( protocol = {"dubbo", "rest"})
public class SayHelloService implements ISayHelloService {
@Override
public String sayyHello() {
return "hello...";
}
}
测试:在浏览器中输入http://localhost:9090/say/sayHello
11、多注册中心
多注册中心的含义就是我们可以将同一个服务注册到不同的服务注册中心。
下面我们演示将同一个服务注册到nacos、zookeeper
application.properties配置多注册中心:
dubbo.registries.nacos.address=nacos://127.0.0.1:8848
dubbo.registries.zookeeper.address=zookeeper://127.0.0.1:2181
服务提供者修如下:
@DubboService( protocol = {"dubbo", "rest"}, registry = {"nacos","zookeeper"})
public class SayHelloService implements ISayHelloService {
@Override
public String sayyHello() {
return "hello...";
}
}
zookeeper注册中心maven依赖 + nacos注册中心依赖:
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.2.1</version>
</dependency>
结果展示:
nacos上服务注册信息:
zookeeper上服务注册信息:
12、服务分组
在dubbo中,如果一个接口有多种实现,那么久需要区分这些实现,因此dubbo有了分组group的概念。
演示分组案例:
实现1代码:
@DubboService( group = "impl1",protocol = {"dubbo", "rest"}, registry = {"nacos","zookeeper"})
public class ChinaSayHelloService implements ISayHelloService {
@Override
public String sayyHello() throws InterruptedException {
return "你好!";
}
}
实现2代码:
@DubboService( group = "impl2",protocol = {"dubbo", "rest"}, registry = {"nacos","zookeeper"})
public class SayHelloService implements ISayHelloService {
@Override
public String sayyHello() {
return "hello...";
}
}
消费者选择分组进行调用:
@Service
public class SayHelloService {
@DubboReference(check = false, group = "impl1")
private ISayHelloService sayHelloService;
public String say() throws InterruptedException {
String s = sayHelloService.sayyHello();
return s;
}
}
nacos服务注册信息展示:
测试结果展示:
13、多版本
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
可以按照以下的步骤进行版本迁移:
第1步:在低压力时间段,先升级一半提供者为新版本
第2步:再将所有消费者升级为新版本
第3步:然后将剩下的一半提供者升级为新版本
我们启动了两个进程,服务提供者代码如下:我们还是分组
@DubboService( group = "impl1",protocol = {"dubbo", "rest"}, registry = {"nacos","zookeeper"},version = "${service.version}")
public class ChinaSayHelloService implements ISayHelloService {
@Override
public String sayyHello() throws InterruptedException {
return "你好!";
}
}
@DubboService( group = "impl2",protocol = {"dubbo", "rest"}, registry = {"nacos","zookeeper"}, version = "${service.version}")
public class SayHelloService implements ISayHelloService {
@Override
public String sayyHello() {
return "hello...";
}
}
我们使用${service.version}来定义版本,通过-D来进行设置
application.properties配置文件中配置:service.version=1.0
复制的服务设置启动参数:-Dserver.port=8082 -Dservice.version=2.0 -Ddubbo.protocols.dubbo.port=20881 -Ddubbo.protocols.rest.port=9091
服务启动成功nacos服务注册信息:
消费者代码:
@Service
public class SayHelloService {
@DubboReference(check = false, group = "impl1", version = "1.0")
private ISayHelloService sayHelloService;
public String say() throws InterruptedException {
String s = sayHelloService.sayyHello();
return s;
}
}
测试结果:
14、分组聚合
如果有一个接口有多种实现,但是我们在调用的时候需要拿到所有实现的一个合集,这个时候我们就可以使用分组聚合特性。
案例如下:
实现1:代码:
@DubboService( group = "impl1",protocol = {"dubbo", "rest"}, registry = {"nacos","zookeeper"})
public class ChinaSayHelloService implements ISayHelloService {
@Override
public List<String> sayyHello() {
ArrayList<String> strings = new ArrayList<>();
strings.add("你好");
return strings;
}
}
实现2代码:
@DubboService( group = "impl2",protocol = {"dubbo", "rest"}, registry = {"nacos","zookeeper"})
public class SayHelloService implements ISayHelloService {
@Override
public List<String> sayyHello() {
ArrayList<String> strings = new ArrayList<>();
strings.add("hello...");
return strings;
}
}
接口定义也修改了,将返回值由String 改为List<String>之所以这样修改是应为返回值是String的话没法合并结果,改成List<String> 就能实现合并,当然如果我们自己做扩展返回类型是String也是可以合并的。
消费者代码:
@Service
public class SayHelloService {
@DubboReference(check = false, group = "impl1,impl2", merger = "true")
private ISayHelloService sayHelloService;
public String say() {
List<String> s = sayHelloService.sayyHello();
String res = "";
for (String s1 : s) {
res = res + s1;
}
return res;
}
}
测试结果展示:
15、引用泛化
泛化的意思是忽略接口的类型,获取服务应用的类型全部是dubbo提供的GenericService类型。
案例:提供者代码:
@DubboService(protocol = {"dubbo", "rest"}, registry = {"nacos", "zookeeper"})
public class SayHelloService implements ISayHelloService {
@Override
public String sayHello(String name) {
return "hello " + name;
}
}
消费者代码:
@Service
public class SayHelloService {
泛化为true的话,interfaceName 必须配置
@DubboReference(check = false, generic = true, interfaceName = "com.wzy.dubbo.ISayHelloService")
private GenericService genericService;
public String say() {
Object sayHello = genericService.$invoke("sayHello", //方法名称。
new String[]{"java.lang.String"}, //所有的函数入参类型数组,顺序是参数顺序。
new Object[]{"wenzongyuan"}); //所有的参数值,顺序是参数顺序。
return sayHello.toString();
}
}
测试结果展示:
16、泛化实现
泛化实现的含义就是服务端的实现实现GenericService,消费端获取到类型是GenericService泛化接口使用泛化的方式进行调用。
服务提供者代码:
@DubboService
public class MyGenericService implements GenericService {
@Override
public Object $invoke(String methodName, String[] parameterTypes, Object[] args) throws GenericException {
if ("sayHi".equals(methodName)) {
return "hi " + args[0];
}
return null;
}
}
服务消费者代码:
@Service
public class SayHelloService {
@DubboReference(check = false)
private GenericService genericService;
public String say() {
Object sayHi = genericService.$invoke("sayHi", new String[]{"java.lang.String"}, new Object[]{"wenzongyuan"});
return sayHi.toString();
}
}
17、回声测试
回声测试用于检测服务是否可用,回声测试按照正常请求流程执行,能够测试整个调用是否通畅,可用于监控。
所有服务自动实现 EchoService
接口,只需将任意服务引用强制转型为 EchoService
,即可使用。
提供者代码 :
@DubboService
public class SayHelloService implements ISayHelloService {
@Override
public String sayHello(String name) {
return "hello " + name;
}
}
消费者代码:
@Service
public class SayHelloService {
@DubboReference(check = false)
private ISayHelloService sayHelloService;
public String say() {
return sayHelloService.sayHello("wenzongyuan");
}
//回声测试
public String ok() {
EchoService echoService = (EchoService) sayHelloService;
Object sayHi = echoService.$echo("ok");
return sayHi.toString();
}
}
测试的rest接口:
@GetMapping("ok")
public String ok() {
return sayHelloService.ok();
}
测试结果展示:
18、dubbo上下文信息RpcContext
上下文中存放的是当前调用过程中所需的环境信息。所有配置信息都将转换为 URL 的参数,RpcContext 是一个 ThreadLocal 的临时状态记录器,当接收到 RPC 请求,或发起 RPC 请求时,RpcContext 的状态都会变化。比如:A 调 B,B 再调 C,则 B 机器上,在 B 调 C 之前,RpcContext 记录的是 A 调 B 的信息,在 B 调 C 之后,RpcContext 记录的是 B 调 C 的信息。
消费者端获取上线文的代码以一些规则:
@Service
public class SayHelloService {
@DubboReference(check = false)
private ISayHelloService sayHelloService;
public String say() {
此时还没发起远程调用,获取本地的host是能获取的
String localHost = RpcContext.getContext().getLocalHost();
但是获取远程的就null
String remoteHost = RpcContext.getContext().getRemoteHost();
注意此时并未发起远程调用,有些信息去上下文获取的时候回抛出异常,比如下面的isConsumerSide()就会抛出空指针异常。
boolean consumerSide1 = RpcContext.getContext().isConsumerSide();
return sayHelloService.sayHello("wenzongyuan");
}
}
提供者获取上下文信息代码:
@DubboService
public class SayHelloService implements ISayHelloService {
@Override
public String sayHello(String name) {
System.out.println("------------provider------------");
boolean providerSide = RpcContext.getContext().isProviderSide();
System.out.println("RpcContext中获取到的是否是提供者:" + providerSide);
String remoteHost = RpcContext.getContext().getRemoteHost();
System.out.println("RpcContext中获取到的远程host:" + remoteHost);
Object[] arguments = RpcContext.getContext().getArguments();
System.out.println("RpcContext中获取到的入参:" + arguments);
注意:每发起RPC调用,上下文状态会变化,举例说明如果在此处发起另外一次RPC调用yyyService.yyy()
yyyService.yyy();
调用结束后,此时本端变成消费端,这里会返回false
boolean isProviderSide = RpcContext.getContext().isProviderSide();
return "hello " + name;
}
}
使用是时候注意一些限制即可。
19、dubbo隐式传参
dubbo隐式传参传参就是使用了上下文信息RpcContext来实现的,就是使用RpcContext的setAttachment系列方法,支持设置String类型,Object类型等。。。
案例如下:
消费者代码:
@Service
public class SayHelloService {
@DubboReference(check = false)
private ISayHelloService sayHelloService;
public String say() {
RpcContext.getContext().setAttachment("user","张三");
return sayHelloService.sayHello("wenzongyuan");
}
}
提供者获取隐式传入的参数值:
@DubboService
public class SayHelloService implements ISayHelloService {
@Override
public String sayHello(String name) {
String user = RpcContext.getContext().getAttachment("user");
System.out.println(user);
return "hello " + name;
}
}
测试结果:
20、本地调用injvm
本地调用使用了 injvm 协议,是一个伪协议,它不开启端口,不发起远程调用,只在 JVM 内直接关联,但执行 Dubbo 的 Filter 链。
21、事件通知
在调用之前、调用之后、出现异常时,会触发 oninvoke
、onreturn
、onthrow
三个事件,可以配置当事件发生时,通知哪个类的哪个方法。
官网案例:
<bean id ="demoCallback" class = "org.apache.dubbo.callback.implicit.NofifyImpl" />
<dubbo:reference id="demoService" interface="org.apache.dubbo.callback.implicit.IDemoService" version="1.0.0" group="cn" >
<dubbo:method name="get" async="true" onreturn = "demoCallback.onreturn" onthrow="demoCallback.onthrow" />
</dubbo:reference>
22、服务降级mock
方式1:在消费端写一个接口的mock实现配置在服务引用上
接口代码:
public interface ISayHelloService {
String sayHello(String name);
String hhhh();
}
mock实现代码:
public class SayHelloServiceMock implements ISayHelloService {
@Override
public String sayHello(String name) {
return "降级兜底";
}
@Override
public String hhhh() {
return "降级兜底hhh";
}
}
消费者代码:
@Service
public class SayHelloService {
@DubboReference(check = false, mock = "com.wzy.consumer.service.SayHelloServiceMock")
private ISayHelloService sayHelloService;
public String say() {
String res = sayHelloService.sayHello("wenzongyuan");
return res;
}
public String hhh() {
String res = sayHelloService.hhhh();
return res;
}
}
提供者我们不启动,然后测试,输入http://localhost:8081/say 返回结果如下:进入容错调用
接下来输入http://localhost:8081/hhh 返回值如下:
方式2:使用fail 或者 force来配置容错返回
fail:表示失败后降级,案例如下:案例里的mock针对的是接口中所有的方法。
@DubboReference(check = false, mock = "fail:return 所有方法的降级兜底数据")
private ISayHelloService sayHelloService;
测试输入:http://localhost:8081/say
测试输入:http://localhost:8081/hhh
当然dubbo也支持为单个方法配置失败降级。
force:表示强制调用降级策略,不会发起远程调用,配置方式跟fail一致。
23、并发控制
1、provider接口级别:限制 com.foo.BarService
的每个方法,服务器端并发执行(或占用线程池线程数)不能超过 10 个:
<dubbo:service interface="com.foo.BarService" executes="10" />
2、provider方法级别:限制 com.foo.BarService
的 sayHello
方法,服务器端并发执行(或占用线程池线程数)不能超过 10 个:
<dubbo:service interface="com.foo.BarService">
<dubbo:method name="sayHello" executes="10" />
</dubbo:service>
3、consumer接口级别:限制 com.foo.BarService
的每个方法,每客户端并发执行(或占用连接的请求数)不能超过 10 个:
<dubbo:service interface="com.foo.BarService" actives="10" />
或者
<dubbo:reference interface="com.foo.BarService" actives="10" />
4、consumer方法级别:限制 com.foo.BarService
的 sayHello
方法,每客户端并发执行(或占用连接的请求数)不能超过 10 个:
<dubbo:service interface="com.foo.BarService">
<dubbo:method name="sayHello" actives="10" />
</dubbo:service>
或者
<dubbo:reference interface="com.foo.BarService">
<dubbo:method name="sayHello" actives="10" />
</dubbo:reference >
并发控制与负载均衡的关联:配置服务的客户端的 loadbalance
属性为 leastactive
,此 Loadbalance 会调用并发数最小的 Provider(Consumer端并发数)。
24、连接控制
1、限制provider的可接受连接数量:限制provider端接受的连接不能超过 10 个 :
<dubbo:provider protocol="dubbo" accepts="10" />
或者
<dubbo:protocol name="dubbo" accepts="10" />
2、限制consumer发起的连接数:限制consumer端服务使用连接不能超过 10 个 :
<dubbo:reference interface="com.foo.BarService" connections="10" />
或者
<dubbo:service interface="com.foo.BarService" connections="10" />
3、延迟连接:延迟连接用于减少长连接数。当有调用发起时,再创建长连接,注意:该配置只对使用长连接的 dubbo 协议生效:
<dubbo:protocol name="dubbo" lazy="true" />
25、优雅停机
Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果用户使用 kill -9 PID
等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID
时,才会执行。
原理:
服务提供方:停止时,先标记为不接收新请求,新请求过来时直接报错,让客户端重试其它机器。然后,检测线程池中的线程是否正在运行,如果有,等待所有线程执行完成,除非超时,则强制关闭。
服务消费方:停止时,不再发起新的调用请求,所有新的调用在客户端即报错。然后,检测有没有请求的响应还没有返回,等待响应返回,除非超时,则强制关闭。
设置方式:设置优雅停机超时时间,缺省超时时间是 10 秒,如果超时则强制关闭。
# dubbo.properties
dubbo.service.shutdown.wait=15000
如果 ShutdownHook 不能生效,可以自行调用,使用tomcat等容器部署的场景,建议通过扩展ContextListener等自行调用以下代码实现优雅停机:
DubboShutdownHook.destroyAll();
26、日志适配
自 2.2.1
开始,dubbo 开始内置 log4j、slf4j、jcl、jdk 这些日志框架的适配[1],也可以通过以下方式显式配置日志输出策略:
-
命令行
java -Ddubbo.application.logger=log4j
-
在
dubbo.properties
中指定dubbo.application.logger=log4j
-
在
dubbo.xml
中配置<dubbo:application logger="log4j" />
27、访问日志
如果你想记录每一次请求信息,可开启访问日志,类似于apache的访问日志。注意:此日志量比较大,请注意磁盘容量。
1、将访问日志输出到当前应用的log4j日志:
<dubbo:protocol accesslog="true" />
2、将访问日志输出到指定文件:
<dubbo:protocol accesslog="http://10.20.160.198/wiki/display/dubbo/foo/bar.log" />
28、dubbo容器类型
默认的容器类型是spring,dubbo默认是依赖spring的一些jar包的。
Spring Container:自动加载 META-INF/spring
目录下的所有 Spring 配置,可手动修改配置,配置 spring 配置加载位置:
dubbo.spring.config=classpath*:META-INF/spring/*.xml
29、线程栈自动dump
当业务线程池满时,我们需要知道线程都在等待哪些资源、条件,以找到系统的瓶颈点或异常点。dubbo通过Jstack自动导出线程堆栈来保留现场,方便排查问题。
指定导出路径:
dubbo.application.dump.directory=/tmp
30、序列化方式优化-->Kryo和FST两种高性能序列化
使用Kryo和FST非常简单,只需要在dubbo RPC的XML配置中添加一个属性即可:
<dubbo:protocol name="dubbo" serialization="kryo"/>
或者
<dubbo:protocol name="dubbo" serialization="fst"/>
要让Kryo和FST完全发挥出高性能,最好将那些需要被序列化的类注册到dubbo系统中,例如,我们可以实现如下回调接口:
public class SerializationOptimizerImpl implements SerializationOptimizer {
public Collection<Class> getSerializableClasses() {
List<Class> classes = new LinkedList<Class>();
classes.add(BidRequest.class);
classes.add(BidResponse.class);
classes.add(Device.class);
classes.add(Geo.class);
classes.add(Impression.class);
classes.add(SeatBid.class);
return classes;
}
}
然后在XML配置中添加:
<dubbo:protocol name="dubbo" serialization="kryo"
optimizer="org.apache.dubbo.demo.SerializationOptimizerImpl"/>
在注册这些类后,序列化的性能可能被大大提升。
31、消费端线程池模型
消费端线程池模型优化:对 2.7.5 版本之前的 Dubbo 应用,尤其是一些消费端应用,当面临需要消费大量服务且并发数比较大的大流量场景时(典型如网关类场景),经常会出现消费端线程数分配过多的问题,具体问题讨论可参见 Need a limited Threadpool in consumer side #2013,改进后的消费端线程池模型,通过复用业务端被阻塞的线程,很好的解决了这个问题。
2.7.5版本之前的线程池模型
我们重点关注 Consumer 部分:
- 业务线程发出请求,拿到一个 Future 实例。
- 业务线程紧接着调用 future.get 阻塞等待业务结果返回。
- 当业务数据返回后,交由独立的 Consumer 端线程池进行反序列化等处理,并调用 future.set 将反序列化后的业务结果置回。
- 业务线程拿到结果直接返回
2.7.5 版本引入的线程池模型
- 业务线程发出请求,拿到一个 Future 实例。
- 在调用 future.get() 之前,先调用 ThreadlessExecutor.wait(),wait 会使业务线程在一个阻塞队列上等待,直到队列中被加入元素。
- 当业务数据返回后,生成一个 Runnable Task 并放入 ThreadlessExecutor 队列
- 业务线程将 Task 取出并在本线程中执行:反序列化业务数据并 set 到 Future。
- 业务线程拿到结果直接返回
这样,相比于老的线程池模型,由业务线程自己负责监测并解析返回结果,免去了额外的消费端线程池开销。