Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"
可以通过 check="false"
关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。
另外,如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check="false"
,总是会返回引用,当服务恢复时,能自动连上
如果在服务提供者没有上线的情况下,我们需要提前将消费者上线,那么就可以关闭启动检查,这样当消费者启动但是不调用服务的情况下不会报错,保证正常启动
<dubbo:reference id="helloService" interface="cn.tedu.service.IHelloService" check="false"/>
:关闭某个服务的启动时检查
public class TestDubbo{ public static void main(String[] args){ ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("appliactionContext.xml"); IHelloService helloService=context.getBean("helloService",IHelloService.class); //现在没有调用服务的情况下不会报错,但是如果调用了HelloService中的方法,那么将会报错 // helloService.sayHello(); context.close(); } }
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'helloService': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: Failed to check the status of the service cn.tedu.service.IHelloService. No provider available for the service cn.tedu.service.IHelloService from the url zookeeper://39.105.123.197:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo-consum01&dubbo=2.5.3&interface=cn.tedu.service.IHelloService&methods=sayHello&pid=10974&revision=0.0.1&side=consumer×tamp=1529667926718 to the consumer 10.18.236.4 use dubbo version 2.5.3 at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:175) at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:103) at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1634) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:254) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1086) at TestDubbo.main(TestDubbo.java:9) Caused by: java.lang.IllegalStateException: Failed to check the status of the service cn.tedu.service.IHelloService. No provider available for the service cn.tedu.service.IHelloService from the url zookeeper://39.105.123.197:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo-consum01&dubbo=2.5.3&interface=cn.tedu.service.IHelloService&methods=sayHello&pid=10974&revision=0.0.1&side=consumer×tamp=1529667926718 to the consumer 10.18.236.4 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.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:168) ... 6 more
关闭所有服务的检查
在消费者的配置文件中配置即可
<dubbo:consumer check="false" />
注册订阅失败的时候报错
<dubbo:registry check="false" />
在 src/main/resource
文件夹下新建 dubbo.properties
dubbo.reference.com.foo.BarService.check=false dubbo.reference.check=false dubbo.consumer.check=false dubbo.registry.check=false
这个在集群部署的时候会用到,比如多台机器提供的是同一个服务,那么当浏览器请求服务的时候到底该调用哪台机器上的服务才会更好,此时就需要用到负载均衡策略
优秀博文
<!-- 多协议配置 --> <dubbo:protocolname="dubbo"port="20880"/> <dubbo:protocolname="rmi"port="1099"/> <!-- 使用dubbo协议暴露服务,直接使用protocol关键词引用上面配置的协议 --> <dubbo:serviceinterface="com.alibaba.hello.api.HelloService"version="1.0.0"ref="helloService"protocol="dubbo"/> <!-- 使用rmi协议暴露服务 --> <dubbo:serviceinterface="com.alibaba.hello.api.DemoService"version="1.0.0"ref="demoService"protocol="rmi"/>
<!-- 多协议配置 --> <dubbo:protocolname="dubbo"port="20880"/> <dubbo:protocolname="hessian"port="8080"/> <!-- 使用多个协议暴露服务 --> <dubbo:serviceid="helloService"interface="com.alibaba.hello.api.HelloService"version="1.0.0"protocol="dubbo,hessian"/>
version
指定版本即可 HelloService
接口,但是有两个实现类,分别为 HelloServiceImpl1
, HelloServiceImpl2
public class HelloServiceImpl1implements IHelloService{ public void sayHello(){ System.out.println("say helloService1"); } } public class HelloServiceImpl2implements IHelloService{ public void sayHello(){ System.out.println("say helloservice2"); } }
<!-- 配置应用名字,用来标识每一个应用,这里的name最好和工程名字一样 --> <dubbo:applicationname="dubbo-provider01"></dubbo:application> <!-- 使用zookeeper注册中心暴露服务 --> <dubbo:registryaddress="zookeeper://39.105.123.197:2181"/> <!-- 配置服务的接口实现类,这样当提供服务调用的接口的时候才能找到对应的实现类 --> <beanid="helloService1"class="cn.tedu.servivceImpl.HelloServiceImpl1"></bean> <!-- 配置服务的接口实现类,这样当提供服务调用的接口的时候才能找到对应的实现类 --> <beanid="helloService2"class="cn.tedu.servivceImpl.HelloServiceImpl2"></bean> <!-- 配置服务提供者,版本为1.0,使用的是helloService1实现类 --> <dubbo:serviceinterface="cn.tedu.service.IHelloService"ref="helloService1"version="1.0"></dubbo:service> <!-- 配置服务提供者,版本为2.0,使用的是helloService2实现类 --> <dubbo:serviceinterface="cn.tedu.service.IHelloService"ref="helloService2"version="2.0"></dubbo:service>
<!--调用2.0版本的服务--> <dubbo:referenceid="helloService2"interface="cn.tedu.service.IHelloService"version="2.0"/> <!--调用1.0版本的服务--> <dubbo:referenceid="helloService1"interface="cn.tedu.service.IHelloService"version="1.0"/>
PayService
,其中实现的类有微信支付 WeChatPayServiceImpl
和支付宝支付 AliPayServiceImpl
,那么我们可以使用分组进行区分两种服务 <beanid="aliPayServiceImpl"class="cn.tedu.serviceImpl.AliPayServiceImpl"></bean> <beanid="weChatPayServiceImpl"class="cn.tedu.serviceImpl.WeChatPayServiceImpl"></bean> <!--使用group区分不同的服务功能 --> <dubbo:servicegroup="alipay"interface="cn.tedu.service.PayService"ref="aliPayServiceImpl"/> <dubbo:servicegroup="weChatPay"interface="cn.tedu.service.PayService"ref="weChatPayServiceImpl"/>
group
指定需要引用的服务 <dubbo:referenceid="alipayService"interface="cn.tedu.service.PayService"group="alipay"/> <dubbo:referenceid="weChatPayService"interface="cn.tedu.service.PayService"group="weChatPay"/>
通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者,另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者
可以全局设置开启令牌验证:
<!--随机token令牌,使用UUID生成--> <dubbo:providerinterface="com.foo.BarService"token="true"/>
或
<!--固定token令牌,相当于密码--> <dubbo:providerinterface="com.foo.BarService"token="123456"/>
也可在服务级别设置:
<!--随机token令牌,使用UUID生成--> <dubbo:serviceinterface="com.foo.BarService"token="true"/>
或
<!--固定token令牌,相当于密码--> <dubbo:serviceinterface="com.foo.BarService"token="123456"/>
还可在协议级别设置:
<!--随机token令牌,使用UUID生成--> <dubbo:protocol name="dubbo" token="true" />
或
<!--固定token令牌,相当于密码--> <dubbo:protocol name="dubbo" token="123456" />
如果事件处理的逻辑能迅速完成,并且不会发起新的 IO 请求,比如只是在内存中记个标识,则直接在 IO 线程上处理更快,因为减少了线程池调度。
但如果事件处理逻辑较慢,或者需要发起新的 IO 请求,比如需要查询数据库,则必须派发到线程池,否则 IO 线程阻塞,将导致不能接收其它请求。
如果用 IO 线程处理事件,又在事件处理过程中发起新的 IO 请求,比如在连接事件中发起登录请求,会报“可能引发死锁”异常,但不会真死锁。
因此,需要通过不同的派发策略和不同的线程池配置的组合来应对不同的场景:
<dubbo:protocolname="dubbo"dispatcher="all"threadpool="fixed"threads="100"/>
Dispatcher
all direct message execution connection
ThreadPool
fixed
固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省) cached
缓存线程池,空闲一分钟自动删除,需要时重建。 limited
可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。 eager
优先创建 Worker
线程池。在任务数量大于 corePoolSize
但是小于 maximumPoolSize
时,优先创建 Worker
来处理任务。当任务数量大于 maximumPoolSize
时,将任务放入阻塞队列中。阻塞队列充满时抛出 RejectedExecutionException
。(相比于 cached
: cached
在任务数量超过 maximumPoolSize
时直接抛出异常而不是将任务放入阻塞队列) Dubbo 支持同一服务向多注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。另外,注册中心是支持自定义扩展的 1 。
比如:中文站有些服务来不及在青岛部署,只在杭州部署,而青岛的其它应用需要引用此服务,就可以将服务同时注册到两个注册中心。
<?xml version="1.0" encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <dubbo:applicationname="world"/> <!-- 多注册中心配置 --> <dubbo:registryid="hangzhouRegistry"address="10.20.141.150:9090"/> <dubbo:registryid="qingdaoRegistry"address="10.20.141.151:9010"default="false"/> <!-- 向多个注册中心注册 --> <dubbo:serviceinterface="com.alibaba.hello.api.HelloService"version="1.0.0"ref="helloService"registry="hangzhouRegistry,qingdaoRegistry"/> </beans>
比如:CRM 有些服务是专门为国际站设计的,有些服务是专门为中文站设计的。
<?xml version="1.0" encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <dubbo:applicationname="world"/> <!-- 多注册中心配置 --> <dubbo:registryid="chinaRegistry"address="10.20.141.150:9090"/> <dubbo:registryid="intlRegistry"address="10.20.154.177:9010"default="false"/> <!-- 向中文站注册中心注册 --> <dubbo:serviceinterface="com.alibaba.hello.api.HelloService"version="1.0.0"ref="helloService"registry="chinaRegistry"/> <!-- 向国际站注册中心注册 --> <dubbo:serviceinterface="com.alibaba.hello.api.DemoService"version="1.0.0"ref="demoService"registry="intlRegistry"/> </beans>
比如:CRM 需同时调用中文站和国际站的 PC2 服务,PC2 在中文站和国际站均有部署,接口及版本号都一样,但连的数据库不一样。
<?xml version="1.0" encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <dubbo:applicationname="world"/> <!-- 多注册中心配置 --> <dubbo:registryid="chinaRegistry"address="10.20.141.150:9090"/> <dubbo:registryid="intlRegistry"address="10.20.154.177:9010"default="false"/> <!-- 引用中文站服务 --> <dubbo:referenceid="chinaHelloService"interface="com.alibaba.hello.api.HelloService"version="1.0.0"registry="chinaRegistry"/> <!-- 引用国际站站服务 --> <dubbo:referenceid="intlHelloService"interface="com.alibaba.hello.api.HelloService"version="1.0.0"registry="intlRegistry"/> </beans>
如果只是测试环境临时需要连接两个不同注册中心,使用竖号分隔多个不同注册中心地址:
<?xml version="1.0" encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <dubbo:applicationname="world"/> <!-- 多注册中心配置,竖号分隔表示同时连接多个不同注册中心,同一注册中心的多个集群地址用逗号分隔 --> <dubbo:registryaddress="10.20.141.150:9090|10.20.154.177:9010"/> <!-- 引用服务 --> <dubbo:referenceid="helloService"interface="com.alibaba.hello.api.HelloService"version="1.0.0"/> </beans>
按组合并返回结果 1 ,比如菜单服务,接口一样,但有多种实现,用group区分,现在消费方需从每种group中调用一次返回结果,合并结果返回,这样就可以实现聚合菜单项。
搜索所有分组
<dubbo:referenceinterface="com.xxx.MenuService"group="*"merger="true"/>
合并指定分组
<dubbo:referenceinterface="com.xxx.MenuService"group="aaa,bbb"merger="true"/>
指定方法合并结果,其它未指定的方法,将只调用一个 Group
<dubbo:referenceinterface="com.xxx.MenuService"group="*"> <dubbo:methodname="getMenuItems"merger="true"/> </dubbo:service>
某个方法不合并结果,其它都合并结果
<dubbo:referenceinterface="com.xxx.MenuService"group="*"merger="true"> <dubbo:methodname="getMenuItems"merger="false"/> </dubbo:service>
指定合并策略,缺省根据返回值类型自动匹配,如果同一类型有两个合并器时,需指定合并器的名称 2
<dubbo:referenceinterface="com.xxx.MenuService"group="*"> <dubbo:methodname="getMenuItems"merger="mymerge"/> </dubbo:service>
指定合并方法,将调用返回结果的指定方法进行合并,合并方法的参数类型必须是返回结果类型本身
<dubbo:referenceinterface="com.xxx.MenuService"group="*"> <dubbo:methodname="getMenuItems"merger=".addAll"/> </dubbo:service>
EchoService
接口,只需将任意服务引用强制转型为 EchoService
,即可使用。 <dubbo:referenceid="memberService"interface="com.xxx.MemberService"/>
// 远程服务引用 MemberService memberService = ctx.getBean("memberService"); EchoService echoService = (EchoService) memberService; // 强制转型为EchoService // 回声测试可用性 String status = echoService.$echo("OK"); assert(status.equals("OK"));
RpcContext
是一个 ThreadLocal
的临时状态记录器,当接收到 RPC 请求,或发起 RPC 请求时,RpcContext 的状态都会变化。比如:A 调 B,B 再调 C,则 B 机器上,在 B 调 C 之前,RpcContext 记录的是 A 调 B 的信息,在 B 调 C 之后,RpcContext 记录的是 B 调 C 的信息。 public class TestDubbo{ public static void main(String[] args){ ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("appliactionContext.xml"); IHelloService helloService=context.getBean("helloService",IHelloService.class); //执行服务,只有执行服务才会认定为消费端 helloService.sayHello(); //测试是否是消费者 Boolean flag=RpcContext.getContext().isConsumerSide(); System.out.println(flag); //获取提供者的ip地址 String ip=RpcContext.getContext().getRemoteHost(); System.out.println(ip); //获取调用者的名称,这里的application是<dubbo:application>标签中的名字 String parameters=RpcContext.getContext().getUrl().getParameter("application"); System.out.println(parameters); context.close(); } }
public class XxxServiceImplimplements XxxService{ public void xxx(){ // 本端是否为提供端,这里会返回true boolean isProviderSide = RpcContext.getContext().isProviderSide(); // 获取调用方IP地址 String clientIP = RpcContext.getContext().getRemoteHost(); // 获取当前服务配置信息,所有配置信息都将转换为URL的参数 String application = RpcContext.getContext().getUrl().getParameter("application"); // 注意:每发起RPC调用,上下文状态会变化 yyyService.yyy(); // 此时本端变成消费端,这里会返回false boolean isProviderSide = RpcContext.getContext().isProviderSide(); } }
可以通过 RpcContext
上的 setAttachment
和 getAttachment
在服务消费方和提供方之间进行参数的隐式传递。 1
setAttachment
设置的 KV 对,在完成下面一次远程调用会被清空,即多次远程调用要多次设置。
RpcContext.getContext().setAttachment("index", "1"); // 隐式传参,后面的远程调用都会隐式将这些参数发送到服务器端,类似cookie,用于框架集成,不建议常规业务使用 xxxService.xxx(); // 远程调用 // ...
public class XxxServiceImplimplements XxxService{ public void xxx(){ // 获取客户端隐式传入的参数,用于框架集成,不建议常规业务使用 String index = RpcContext.getContext().getAttachment("index"); } }
基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。 1
在 consumer.xml 中配置:
<dubbo:referenceid="fooService"interface="com.alibaba.foo.FooService"> <dubbo:methodname="findFoo"async="true"/> </dubbo:reference> <dubbo:referenceid="barService"interface="com.alibaba.bar.BarService"> <dubbo:methodname="findBar"async="true"/> </dubbo:reference>
调用代码:
// 此调用会立即返回null fooService.findFoo(fooId); // 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future Future<Foo> fooFuture = RpcContext.getContext().getFuture(); // 此调用会立即返回null barService.findBar(barId); // 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future Future<Bar> barFuture = RpcContext.getContext().getFuture(); // 此时findFoo和findBar的请求同时在执行,客户端不需要启动多线程来支持并行,而是借助NIO的非阻塞完成 // 如果foo已返回,直接拿到返回值,否则线程wait住,等待foo返回后,线程会被notify唤醒 Foo foo = fooFuture.get(); // 同理等待bar返回 Bar bar = barFuture.get(); // 如果foo需要5秒返回,bar需要6秒返回,实际只需等6秒,即可获取到foo和bar,进行接下来的处理。
你也可以设置是否等待消息发出: 2
sent="true" sent="false"
<dubbo:methodname="findFoo"async="true"sent="true"/>
如果你只是想异步,完全忽略返回值,可以配置 return="false"
,以减少 Future 对象的创建和管理成本:
<dubbo:methodname="findFoo"async="true"return="false"/>
2.0.6
及其以上版本支持 ↩
异步总是不等待返回 ↩
dubbo的本地存根的原理是:远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,那么就在服务消费者这一端提供了一个Stub类,然后当消费者调用provider方提供的dubbo服务时,客户端生成 Proxy 实例,这个Proxy实例就是我们正常调用dubbo远程服务要生成的代理实例,然后消费者这方会把 Proxy 通过构造函数传给 消费者方的Stub ,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。会通过代理类去完成这个调用,这样在Stub类中,就可以做一些额外的事,来对服务的调用过程进行优化或者容错的处理。附图:
总结:如果消费者想用在调用远程服务的同时还想在之前或者之后实现自己的部分逻辑,那么就需要在消费者端定义一个代理类,其实在消费者调用服务的时候,实际上是调用的代理类。不过其中代理类返回的数据是可以传递给服务提供者的
public interface UserInterface{ public User getUserById(Integer id); }
public class UserServiceimplements UserInterface{ public User getUserById(Integer id){ User user = new User() ; user.setId(id); user.setName("hu"); return user; } }
<dubbo:serviceinterface="org.huxin.dubbo.test.user.service.UserInterface"ref="userService"protocol="dubbo"retries="0"/> <beanid="userService"class="org.huxin.dubbo.test.user.service.impl.UserService"/>
3.服务消费者的Stub类
public class UserServiceStubimplements UserInterface{ //必须定义这个接口,以便接收dubbo在调用远程服务生成的服务代理类 private UserInterface userLocalService ; //这个构造函数必须要提供,dubbo框架会在消费者这一方调用这个方法 public UserServiceStub(UserInterface userLocalService ){ this.userLocalService = userLocalService ; } public User getUserById(Integer id){ User user = null ; try { if (id == 1) { user = this.userLocalService.getUserById(id) ; }else { user = new User(); user.setName("系统用户"); } }catch(Exception e) { user = new User(); user.setName("异常用户"); } return user ; } }
<dubbo:referenceid="userService"interface="org.huxin.dubbo.test.user.service.UserInterface" stub="org.huxin.dubbo.test.UserServiceStub" protocol="dubbo"/>
5.测试代码
@Test public void testGetUserById(){ Integer id = 2 ; UserInterface userService = context.getBean(UserInterface.class) ; User user = userService.getUserById( id) ; System.out.println(user.getName()); }
getUserById(id)
这个方法是代理类 UserServiceStub
的方法,返回的User对象也是这个这个方法返回的 粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。
粘滞连接将自动开启 延迟连接 ,以减少长连接数。
<dubbo:protocolname="dubbo"sticky="true"/>