这里对dubbo的一些配置的使用场景做下介绍。
启动服务检查
Dubbo默认会在启动时检查依赖的服务是否可用,不可用时会抛出异常,组织Spring初始化完成,以便在上线时可以及早发现问题。通常是调用方在dubbo:reference
标签里配置check
属性。在前面的例子中,没有配置默认为true
,这里停止服务提供者dubbo-order
启动dubbo-user
。
<dubbo:reference id="orderProvider"
interface="com.lucky.api.IOrderServices"/>
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new
ClassPathXmlApplicationContext("order-consumer.xml");
IOrderServices orderProvider = (IOrderServices) context.getBean("orderProvider");
// OrderRequest request = new OrderRequest();
// request.setName("lucky");
// OrderResponse orderResponse = orderProvider.doOrder(request);
// System.out.println(orderResponse.toString());
System.in.read();
}
Caused by: java.lang.IllegalStateException: Failed to check the status of the service com.lucky.api.IOrderServices. No provider available for the service com.lucky.api.IOrderServices from the url zookeeper://localhost:2181/com.alibaba.dubbo.registry.RegistryService?application=order-consumer&dubbo=2.5.3&interface=com.lucky.api.IOrderServices&methods=doOrder&owner=lucky&pid=25243&side=consumer×tamp=1577761649285 to the consumer 10.100.171.172 use dubbo version 2.5.3
at com.alibaba.dubbo.config.ReferenceConfig.createProxy(ReferenceConfig.java:420)
at com.alibaba.dubbo.config.ReferenceConfig.init(ReferenceConfig.java:300)
at com.alibaba.dubbo.config.ReferenceConfig.get(ReferenceConfig.java:138)
at com.alibaba.dubbo.config.spring.ReferenceBean.getObject(ReferenceBean.java:65)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport$1.run(FactoryBeanRegistrySupport.java:121)
... 9 more
可以看到dubbo-user
启动失败,抛出不能检测IOrderServices
这个服务的状态。
在order-consumer.xml
配置文件里关闭服务检测选项,再次启动dubbo-user
,服务可以正常启动。
<dubbo:reference id="orderProvider" check="false"
interface="com.lucky.api.IOrderServices"/>
释放启动服务里的调用代码,这里应该会抛出dubbo-provider
服务访问失败的错误。如下所示,是因为跳过了检测直接去调用,同时dubbo-provider
服务没有启动,所以会出现以下错误。
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new
ClassPathXmlApplicationContext("order-consumer.xml");
IOrderServices orderProvider = (IOrderServices) context.getBean("orderProvider");
OrderRequest request = new OrderRequest();
request.setName("lucky");
OrderResponse orderResponse = orderProvider.doOrder(request);
System.out.println(orderResponse.toString());
System.in.read();
}
Exception in thread "main" com.alibaba.dubbo.rpc.RpcException: Forbid consumer 10.100.171.172 access service com.lucky.api.IOrderServices from registry localhost:2181 use dubbo version 2.5.3, Please check registry access list (whitelist/blacklist).
at com.alibaba.dubbo.registry.integration.RegistryDirectory.doList(RegistryDirectory.java:579)
at com.alibaba.dubbo.rpc.cluster.directory.AbstractDirectory.list(AbstractDirectory.java:73)
at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.list(AbstractClusterInvoker.java:260)
at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:219)
at com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:72)
at com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:52)
at com.alibaba.dubbo.common.bytecode.proxy0.doOrder(proxy0.java)
at com.lucky.App.main(App.java:20)
设置为false的一些场景
- 在开发测试阶段,并不需要启动的时候就检查,更需要的是服务的更快启动,所以这个时候可以选择关闭这个功能的。
- 另外如果出现循环依赖调用的时候,就必须关闭这个功能了,因为这个时候必须有一个服务先启动起来。
另外这个check
属性在很多标签里都可以配置。
dubbo:consumer
:表示如果没有服务提供者的时候,会报错。可以统一关闭所有服务引用的检查,是消费端全局的一个配置。
在这两个标签里使用的时候,不是特别明白😂
dubbo:registry
:连接注册中心,注册失败的时候会报错dubbo:provider
:
多协议支持
dubbo支持的协议:dubbo、rmi、hessian、webservice、http、thrift。其中dubbo和rmi是基于tcp的,hessian、webservice、http是基于http的。
- 不同的协议,在不同的场景的性能是不一样的,比如大数据量的时候适合使用短连接协议,小数据量适合使用长协议连接。
- 在项目改造时,也会存在多协议共存的现象。项目最初使用的协议和需要改造使用的协议是不一样的,会同时存在。
hessian是一个http协议所以需要运行在容器里发布,配置使用的容器为jetty
使用hessian协议通信
-
引入hessian依赖
<dependency> <groupId>com.caucho</groupId> <artifactId>hessian</artifactId> <version>4.0.38</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty</artifactId> <version>6.1.26</version> </dependency>
-
配置文件增加hessian协议
<dubbo:protocol name="hessian" port="8090" server="jetty"/>
由于hessian是基于http协议的,所以需要运行在容器里发布,这里配置的容器是jetty。
-
为发布的服务指定协议
<dubbo:service interface="com.lucky.api.IOrderServices" ref="orderServices" protocol="hessian"/>
为需要发布的服务配置
protocol
属性。 -
启动服务查看启动日志
2019-12-31 15:12:55,366 INFO [com.alibaba.dubbo.config.AbstractConfig] - [DUBBO] Export dubbo service com.lucky.api.IOrderServices to url hessian://10.100.171.172:8090/com.lucky.api.IOrderServices?anyhost=true&application=order-provider&dubbo=2.5.3&interface=com.lucky.api.IOrderServices&methods=doOrder&owner=lucky&pid=26229&server=jetty&side=provider×tamp=1577776375142, dubbo version: 2.5.3, current host: 127.0.0.1
可以看出,
IOrderServices
服务已经通过hessian
协议发布出去了 -
同样消费端,引入
hessian
依赖,同时指定reference
标签使用的协议<dependency> <groupId>com.caucho</groupId> <artifactId>hessian</artifactId> <version>4.0.38</version> </dependency>
<dubbo:reference id="orderProvider" interface="com.lucky.api.IOrderServices" protocol="hessian"/>
可以看到调用成功。
多个协议同时存在
上面的通信还是一个协议hessian
。这里我们让发布端发布两种协议,修改配置文件。
<dubbo:service interface="com.lucky.api.IOrderServices"
ref="orderServices" protocol="hessian,dubbo"/>
启动服务,查看启动日志如下,对于服务IOrderServices
发布了两个不同的协议,这样我们在客户端,就可以根据需要来使用不同协议的服务。
2019-12-31 15:33:48,474 INFO [com.alibaba.dubbo.config.AbstractConfig] - [DUBBO] Export dubbo service com.lucky.api.IOrderServices to url hessian://10.100.171.172:8090/com.lucky.api.IOrderServices?anyhost=true&application=order-provider&dubbo=2.5.3&interface=com.lucky.api.IOrderServices&methods=doOrder&owner=lucky&pid=26259&server=jetty&side=provider×tamp=1577777628194, dubbo version: 2.5.3, current host: 127.0.0.1
2019-12-31 15:33:49,243 INFO [com.alibaba.dubbo.config.AbstractConfig] - [DUBBO] Export dubbo service com.lucky.api.IOrderServices to url dubbo://10.100.171.172:20880/com.lucky.api.IOrderServices?anyhost=true&application=order-provider&dubbo=2.5.3&interface=com.lucky.api.IOrderServices&methods=doOrder&owner=lucky&pid=26259&side=provider×tamp=1577777629241, dubbo version: 2.5.3, current host: 127.0.0.1
多注册中心
dubbo对于服务发布端支持同一服务向多注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,对于消费端可以同时引用注册在不同注册中心上的同名服务。
同一个服务向多个注册中心注册
比如:中文站有些服务来不及在青岛部署,只在杭州部署,而青岛的其它应用需要引用此服务,就可以将服务同时注册到两个注册中心。
<dubbo:registry id="zkOne" protocol="zookeeper" address="localhost:2181" />
<dubbo:registry id="zkTwo" protocol="zookeeper" address="192.168.21.76:2181"/>
<dubbo:service interface="com.lucky.api.IOrderServices"
ref="orderServices" protocol="hessian,dubbo" registry="zkOne,zkTwo"/>
不同的服务向不同的注册中心注册
比如:CRM有些服务是专门为国际站设计的,有些服务是专门为中文站设计的
<dubbo:registry id="zkOne" protocol="zookeeper" address="localhost:2181" />
<dubbo:registry id="zkTwo" protocol="zookeeper" address="192.168.21.76:2181"/>
<dubbo:service interface="com.lucky.api.IOrderServices"
ref="orderServices" protocol="hessian,dubbo" registry="zkOne"/>
<dubbo:service interface="com.lucky.api.IOrderServices1"
ref="orderServices1" protocol="hessian,dubbo" registry="zkTwo"/>
消费端 相同的服务引用使用不同的注册中心
比如:CRM需同时调用中文站和国际站的PC2服务,PC2在中文站和国际站均有部署,接口及版本号都一样,但连的数据库不一样。
<!-- 多注册中心配置 -->
<dubbo:registry id="chinaRegistry" address="10.20.141.150:9090" />
<dubbo:registry id="intlRegistry" address="10.20.154.177:9010" default="false" />
<!-- 引用中文站服务 -->
<dubbo:reference id="chinaHelloService"
interface="com.alibaba.hello.api.HelloService" registry="chinaRegistry" />
<!-- 引用国际站站服务 -->
<dubbo:reference id="intlHelloService"
interface="com.alibaba.hello.api.HelloService" registry="intlRegistry" />
多版本支持
在服务迭代升级的过程中,会出现不兼容升级,这个时候就需要使用版本号,来区分不同版本服务的调用。
<dubbo:service interface="com.lucky.api.IOrderServices"
ref="orderServices" protocol="hessian,dubbo" version="1.0"/>
<dubbo:service interface="com.lucky.api.IOrderServices"
ref="orderServices1" protocol="hessian,dubbo" version="2.0"/>
<dubbo:reference id="orderProvider"
interface="com.lucky.api.IOrderServices" protocol="hessian" version="1.0"/>
服务发布端,为dubbo:service
标签配置不同的ref
实现类,同时设置不同的版本号version
,在服务消费端,使用该服务时,通过指定版本号就可以调用不同版本的服务了。
异步调用
这里说的异步调用应该是非阻塞的NIO调用。
hessian
不支持异步调用,因为hessian
是使用http
协议,是短连接,所以不支持回调功能。
异步调用只支持dubbo
协议,在消费端的dubbo:reference
标签里配置async="true"
即,该引用服务下的所有方法均异步调用如下所示
<dubbo:reference id="orderProvider"
interface="com.lucky.api.IOrderServices" protocol="dubbo" version="2.0"
async="true" timeout="6000"/>
同时也可以指定服务的某些特定方法使用异步调用,配置形式如下所示:
<dubbo:reference id="orderProvider"
interface="com.lucky.api.IOrderServices" protocol="dubbo" version="2.0"
>
<dubbo:method name="doOrder" async="true" timeout="6000"/>
</dubbo:reference>
如上,还多添加了一个超时时间的配置,为了演示,这里在IOrderServices
服务的doOrder
模拟了下耗时代码如下,实际使用中服务端可以不做任何多余的修改配置:
@Override
public OrderResponse doOrder(OrderRequest request) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("曾经来过版本2" + request);
OrderResponse response = new OrderResponse();
response.setCode("1000");
response.setMsg("处理成功版本2");
return response;
}
消费端的使用:
public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
ClassPathXmlApplicationContext context = new
ClassPathXmlApplicationContext("order-consumer.xml");
IOrderServices orderProvider = (IOrderServices) context.getBean("orderProvider");
OrderRequest request = new OrderRequest();
request.setName("lucky");
orderProvider.doOrder(request);
Future<OrderResponse> response = RpcContext.getContext().getFuture();
System.out.println("aaaa");
OrderResponse orderResponse = response.get();
System.out.println(orderResponse.toString());
System.in.read();
}
服务只订阅
意思是说一个服务在启动和注册中心通信的时候,可以通过配置dubbo:registry
标签的register
来设置该服务只订阅注册中心,而不会将自身发布的服务注册到注册中心。
<dubbo:registry protocol="zookeeper" address="localhost:2181" register="false"/>
启动服务,对比不对此设置时的启动日志发现缺少如下内容,这个时候只是做了 发布服务和启动netty,而没有向 注册中心 zookeeper 注册:
2019-12-31 17:52:53,363 INFO [com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry] - [DUBBO] Register: dubbo://10.100.171.172:20880/com.lucky.api.IOrderServices2?anyhost=true&application=order-provider&dubbo=2.5.3&interface=com.lucky.api.IOrderServices&methods=doOrder&owner=lucky&pid=26390&revision=2.0&side=provider×tamp=1577785973357&version=2.0, dubbo version: 2.5.3, current host: 127.0.0.1
服务只注册
只注册正好跟前面的只订阅相反,这个时候可以向注册中心注册,但是,消费端却不能够读到服务。
应用场景
如果有两套环境,两个注册中心,有一个服务只在其中一个注册中心部署,另一个注册中心还没来得及部署,而两个注册中心的其他服务都需要依赖此服务。此时,可以让服务提供者方只注册服务到另一注册中心,而不从另一注册中心订阅服务。这样当该服务作为消费者调用其他服务的时候,只会从一个注册中心查找。
配置方式:
<dubbo:registry subscribe="false" address="localhost:2181"/>
负载均衡
在集群环境,dubbo为服务提供者提供了多种均衡策略。配置方式如下:
<dubbo:service interface="com.lucky.api.IOrderServices"
ref="orderServices" protocol="dubbo" version="1.0" loadbalance="random"/>
在服务端的dubbo:service
标签配置loadbalance
属性。dubbo支持的均衡策略有以下几种:
-
Random LoadBalance
随机,按权重设置随机概率。
在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
-
RoundRobin LoadBalance
轮循,按公约后的权重设置轮循比率。
存在慢的提供者累积请求问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
-
LastActive LoadBalance
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
-
ConsistentHash LoadBalance
一致性Hash,相同参数的请求总是发到同一提供者。
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
连接超时
消费端在调用服务端时,会因为网络原因或者服务耗时等因素出现超时,可以配置timeout
属性来设置超时时间,在异步特性里使用了该属性配置。
同时有一点比较重要的是,对于超时等这种属性配置优先级的选择规则如下:
集群容错
在集群环境,消费端调用服务端时服务端会存在出错的场景,那这个时候消费端如何处理。dubbo默认的是failover,重试机制。使用方式:
<dubbo:service interface="com.lucky.api.IOrderServices"
ref="orderServices" protocol="dubbo" version="1.0" cluster="failover" retries="2"/>
为dubbo:service
标签配置cluster
属性,当为failover
重试机制时,可以额外配置下重试的次数,默认为2
,即会尝试三次,第一次不算,然后重试两次。
dubbo还支持如下容错机制:
-
Failover Cluster
失败自动切换,当出现失败,重试其它服务器。(默认方式)
通常用于读操作,但重试会带来更长延迟。
可通过retries="2"来设置重试次数(不含第一次)。
-
Failfast Cluster
快速失败,只发起一次调用,失败立即报错。
通常用于非幂等性的写操作,比如新增记录。
-
Failsafe Cluster
失败安全,出现异常时,直接忽略。
通常用于写入审计日志等操作。
-
Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。
通常用于消息通知操作。
-
Forking Cluster
并行调用多个服务器,只要一个成功即返回。
通常用于实时性要求较高的读操作,但需要浪费更多服务资源。
可通过forks="2"来设置最大并行数。
-
Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错。(2.1.0开始支持)
通常用于通知所有提供者更新缓存或日志等本地资源信息。
日志管理
dubbo 也可以将日志信息记录或者保存到文件中的
-
使用accesslog输出到log4j
<dubbo:protocol accesslog="true" name="dubbo" port="20880"/>
-
输出到文件
<dubbo:protocol accesslog="http://localhost/log.txt" name="dubbo" port="20880"/>