Dubbo使用总结

2019-12-31 15:12:30 浏览数 (1)

文章目录

1. Dubbo常见问题

1.1. 官方文档

1.2. 启动检查

1.2.1. 配置方式

1.2.1.1. 关闭某个服务的检查

1.2.1.2. 关闭所有服务的检查

1.2.1.3. 关闭注册中心启动时检查

1.2.1.4. dubbo.properties

1.3. 负载均衡策略

1.4. 多协议

1.4.1. 不同服务不同协议

1.4.2. 多协议暴露服务

1.5. 多版本

1.5.1. 实现

1.6. 服务分组

1.6.1. 服务

1.6.2. 引用

1.7. 令牌验证

1.8. dubbo控制台的安装部署

1.9. 线程模型

1.10. 多注册中心

1.10.1. 多注册中心注册

1.10.2. 不同服务使用不同注册中心

1.10.3. 多注册中心引用

1.11. 分组聚合

1.11.1. 配置

1.12. 配置文件覆盖策略

1.13. 回声测试

1.14. 上下文信息

1.14.1. 服务消费方

1.14.2. 服务提供方

1.15. 隐式参数

1.15.1. 在服务消费方端设置隐式参数

1.15.2. 在服务提供方端获取隐式参数

1.16. 异步调用

1.17. 本地存根

1.17.1. 实现步骤

1.17.2. 总结

1.18. 本地伪装

1.19. 延迟暴露

1.20. 并发控制

1.21. 连接控制

1.22. 延迟连接

1.23. 粘滞连接

Dubbo常见问题

官方文档

  • 中文文档

启动检查

Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true" 可以通过 check="false" 关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。 另外,如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check="false",总是会返回引用,当服务恢复时,能自动连上 如果在服务提供者没有上线的情况下,我们需要提前将消费者上线,那么就可以关闭启动检查,这样当消费者启动但是不调用服务的情况下不会报错,保证正常启动

配置方式

关闭某个服务的检查
  1. <dubbo:reference id="helloService" interface="cn.tedu.service.IHelloService" check="false"/> :关闭某个服务的启动时检查

这个只会关闭当前的服务器的检查,还是会检查其他的服务

在没有对应服务提供者的情况下如果调用这个服务那么将会报错

在消费者中配置

代码语言:javascript复制
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();
	}
}
  1. 如果没有关闭检查,那么会出现如下的错误
代码语言:javascript复制
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&timestamp=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&timestamp=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" />

dubbo.properties
  • 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中存在8中不同的协议,这些协议的作用都是不同的,此时我们需要根据服务的功能来使用不同的协议,比如我们需要上传文件,那么就需要能够传输大文件的协议
  • 默认是dubbo协议,也是用的最多的协议

不同服务不同协议

代码语言:javascript复制
<!-- 多协议配置 -->
    <dubbo:protocol name="dubbo" port="20880" />
    <dubbo:protocol name="rmi" port="1099" />

    <!-- 使用dubbo协议暴露服务,直接使用protocol关键词引用上面配置的协议 -->
    <dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" protocol="dubbo" />

    <!-- 使用rmi协议暴露服务 -->
    <dubbo:service interface="com.alibaba.hello.api.DemoService" version="1.0.0" ref="demoService" protocol="rmi" />

多协议暴露服务

代码语言:javascript复制
<!-- 多协议配置 -->
    <dubbo:protocol name="dubbo" port="20880" />
    <dubbo:protocol name="hessian" port="8080" />

    <!-- 使用多个协议暴露服务 -->
    <dubbo:service id="helloService" interface="com.alibaba.hello.api.HelloService" version="1.0.0" protocol="dubbo,hessian" />

多版本

  • 当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。 可以按照以下的步骤进行版本迁移:
    1. 在低压力时间段,先升级一半提供者为新版本
    2. 再将所有消费者升级为新版本
    3. 然后将剩下的一半提供者升级为新版本
  • 直接使用version指定版本即可

实现

  • 我们有一个HelloService接口,但是有两个实现类,分别为HelloServiceImpl1,HelloServiceImpl2
代码语言:javascript复制
public class HelloServiceImpl1 implements IHelloService {

	public void sayHello() {
		System.out.println("say helloService1");
	}

}

public class HelloServiceImpl2 implements IHelloService {

	public void sayHello() {
		System.out.println("say helloservice2");
	}

}
  • 配置服务提供者,需要根据版本的不同提供服务
代码语言:javascript复制
<!-- 配置应用名字,用来标识每一个应用,这里的name最好和工程名字一样 -->
	<dubbo:application name="dubbo-provider01"></dubbo:application>
	
	<!-- 使用zookeeper注册中心暴露服务 -->
	<dubbo:registry address="zookeeper://39.105.123.197:2181" />
	
	<!-- 配置服务的接口实现类,这样当提供服务调用的接口的时候才能找到对应的实现类 -->
	<bean id="helloService1" class="cn.tedu.servivceImpl.HelloServiceImpl1"></bean>
	
	<!-- 配置服务的接口实现类,这样当提供服务调用的接口的时候才能找到对应的实现类 -->
	<bean id="helloService2" class="cn.tedu.servivceImpl.HelloServiceImpl2"></bean>
	
    
	<!-- 配置服务提供者,版本为1.0,使用的是helloService1实现类 -->
	<dubbo:service interface="cn.tedu.service.IHelloService" ref="helloService1" version="1.0"></dubbo:service>
		
	<!-- 配置服务提供者,版本为2.0,使用的是helloService2实现类 -->
	<dubbo:service interface="cn.tedu.service.IHelloService" ref="helloService2" version="2.0"></dubbo:service>
  • 配置服务消费者,需要指定版本号区分调用的服务
    • 根据版本号区分调用哪个服务
代码语言:javascript复制
<!--调用2.0版本的服务-->
<dubbo:reference id="helloService2" interface="cn.tedu.service.IHelloService" version="2.0"/>

<!--调用1.0版本的服务-->
<dubbo:reference id="helloService1" interface="cn.tedu.service.IHelloService" version="1.0"/>

服务分组

  • 当一个接口有多种实现的时候,我们可以使用分组区分调用的服务功能
  • 假设一个支付的接口PayService,其中实现的类有微信支付WeChatPayServiceImpl和支付宝支付AliPayServiceImpl,那么我们可以使用分组进行区分两种服务

服务

  • 在服务提供者的配置文件中定义
代码语言:javascript复制
<bean id="aliPayServiceImpl" class="cn.tedu.serviceImpl.AliPayServiceImpl"></bean>
<bean id="weChatPayServiceImpl" class="cn.tedu.serviceImpl.WeChatPayServiceImpl"></bean>

<!--使用group区分不同的服务功能 -->
<dubbo:service group="alipay" interface="cn.tedu.service.PayService" ref="aliPayServiceImpl" />

<dubbo:service group="weChatPay" interface="cn.tedu.service.PayService" ref="weChatPayServiceImpl" />

引用

  • 在消费者的配置文件中配置即可,使用group指定需要引用的服务
代码语言:javascript复制
<dubbo:reference id="alipayService" interface="cn.tedu.service.PayService" group="alipay"/>

<dubbo:reference id="weChatPayService" interface="cn.tedu.service.PayService" group="weChatPay"/>

令牌验证

通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者,另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者

可以全局设置开启令牌验证:

代码语言:javascript复制
<!--随机token令牌,使用UUID生成-->
<dubbo:provider interface="com.foo.BarService" token="true" />

代码语言:javascript复制
<!--固定token令牌,相当于密码-->
<dubbo:provider interface="com.foo.BarService" token="123456" />

也可在服务级别设置:

代码语言:javascript复制
<!--随机token令牌,使用UUID生成-->
<dubbo:service interface="com.foo.BarService" token="true" />

代码语言:javascript复制
<!--固定token令牌,相当于密码-->
<dubbo:service interface="com.foo.BarService" token="123456" />

还可在协议级别设置:

代码语言:javascript复制
<!--随机token令牌,使用UUID生成-->
<dubbo:protocol name="dubbo" token="true" />

代码语言:javascript复制
<!--固定token令牌,相当于密码-->
<dubbo:protocol name="dubbo" token="123456" />

dubbo控制台的安装部署

  • 下载dubbo-admin.war : 链接:https://pan.baidu.com/s/1ggeIIHX 密码:ck4h
  • 优秀博文

线程模型

如果事件处理的逻辑能迅速完成,并且不会发起新的 IO 请求,比如只是在内存中记个标识,则直接在 IO 线程上处理更快,因为减少了线程池调度。

但如果事件处理逻辑较慢,或者需要发起新的 IO 请求,比如需要查询数据库,则必须派发到线程池,否则 IO 线程阻塞,将导致不能接收其它请求。

如果用 IO 线程处理事件,又在事件处理过程中发起新的 IO 请求,比如在连接事件中发起登录请求,会报“可能引发死锁”异常,但不会真死锁。

因此,需要通过不同的派发策略和不同的线程池配置的组合来应对不同的场景:

代码语言:javascript复制
<dubbo:protocol name="dubbo" dispatcher="all" threadpool="fixed" threads="100" />

Dispatcher

  • all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
  • direct 所有消息都不派发到线程池,全部在 IO 线程上直接执行。
  • message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
  • execution 只请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
  • connection 在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。

ThreadPool

  • fixed 固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)
  • cached 缓存线程池,空闲一分钟自动删除,需要时重建。
  • limited 可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。
  • eager 优先创建Worker线程池。在任务数量大于corePoolSize但是小于maximumPoolSize时,优先创建Worker来处理任务。当任务数量大于maximumPoolSize时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException。(相比于cached:cached在任务数量超过maximumPoolSize时直接抛出异常而不是将任务放入阻塞队列)

多注册中心

Dubbo 支持同一服务向多注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。另外,注册中心是支持自定义扩展的 1。

多注册中心注册

比如:中文站有些服务来不及在青岛部署,只在杭州部署,而青岛的其它应用需要引用此服务,就可以将服务同时注册到两个注册中心。

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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:application name="world"  />
    <!-- 多注册中心配置 -->
    <dubbo:registry id="hangzhouRegistry" address="10.20.141.150:9090" />
    <dubbo:registry id="qingdaoRegistry" address="10.20.141.151:9010" default="false" />
    <!-- 向多个注册中心注册 -->
    <dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" registry="hangzhouRegistry,qingdaoRegistry" />
</beans>

不同服务使用不同注册中心

比如:CRM 有些服务是专门为国际站设计的,有些服务是专门为中文站设计的。

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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:application name="world"  />
    <!-- 多注册中心配置 -->
    <dubbo:registry id="chinaRegistry" address="10.20.141.150:9090" />
    <dubbo:registry id="intlRegistry" address="10.20.154.177:9010" default="false" />
    <!-- 向中文站注册中心注册 -->
    <dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" registry="chinaRegistry" />
    <!-- 向国际站注册中心注册 -->
    <dubbo:service interface="com.alibaba.hello.api.DemoService" version="1.0.0" ref="demoService" registry="intlRegistry" />
</beans>

多注册中心引用

比如:CRM 需同时调用中文站和国际站的 PC2 服务,PC2 在中文站和国际站均有部署,接口及版本号都一样,但连的数据库不一样。

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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:application name="world"  />
    <!-- 多注册中心配置 -->
    <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" version="1.0.0" registry="chinaRegistry" />
    <!-- 引用国际站站服务 -->
    <dubbo:reference id="intlHelloService" interface="com.alibaba.hello.api.HelloService" version="1.0.0" registry="intlRegistry" />
</beans>

如果只是测试环境临时需要连接两个不同注册中心,使用竖号分隔多个不同注册中心地址:

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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:application name="world"  />
    <!-- 多注册中心配置,竖号分隔表示同时连接多个不同注册中心,同一注册中心的多个集群地址用逗号分隔 -->
    <dubbo:registry address="10.20.141.150:9090|10.20.154.177:9010" />
    <!-- 引用服务 -->
    <dubbo:reference id="helloService" interface="com.alibaba.hello.api.HelloService" version="1.0.0" />
</beans>

分组聚合

按组合并返回结果 1,比如菜单服务,接口一样,但有多种实现,用group区分,现在消费方需从每种group中调用一次返回结果,合并结果返回,这样就可以实现聚合菜单项。

配置

搜索所有分组

代码语言:javascript复制
<dubbo:reference interface="com.xxx.MenuService" group="*" merger="true" />

合并指定分组

代码语言:javascript复制
<dubbo:reference interface="com.xxx.MenuService" group="aaa,bbb" merger="true" />

指定方法合并结果,其它未指定的方法,将只调用一个 Group

代码语言:javascript复制
<dubbo:reference interface="com.xxx.MenuService" group="*">
    <dubbo:method name="getMenuItems" merger="true" />
</dubbo:service>

某个方法不合并结果,其它都合并结果

代码语言:javascript复制
<dubbo:reference interface="com.xxx.MenuService" group="*" merger="true">
    <dubbo:method name="getMenuItems" merger="false" />
</dubbo:service>

指定合并策略,缺省根据返回值类型自动匹配,如果同一类型有两个合并器时,需指定合并器的名称 2

代码语言:javascript复制
<dubbo:reference interface="com.xxx.MenuService" group="*">
    <dubbo:method name="getMenuItems" merger="mymerge" />
</dubbo:service>

指定合并方法,将调用返回结果的指定方法进行合并,合并方法的参数类型必须是返回结果类型本身

代码语言:javascript复制
<dubbo:reference interface="com.xxx.MenuService" group="*">
    <dubbo:method name="getMenuItems" merger=".addAll" />
</dubbo:service>

配置文件覆盖策略

  • 消费者中的相同的配置将会覆盖提供者的配置

回声测试

  • 回声测试用于检测服务是否可用,回声测试按照正常请求流程执行,能够测试整个调用是否通畅,可用于监控。所有服务自动实现 EchoService 接口,只需将任意服务引用强制转型为 EchoService,即可使用。
  • Spring 配置:
代码语言:javascript复制
<dubbo:reference id="memberService" interface="com.xxx.MemberService" />
  • 代码:
代码语言:javascript复制
// 远程服务引用
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 的信息。
  • RpcContext的状态是随时变化的,只会存储最近调用的信息

服务消费方

代码语言:javascript复制
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();
	}
}

服务提供方

代码语言:javascript复制
public class XxxServiceImpl implements 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 上的 setAttachmentgetAttachment 在服务消费方和提供方之间进行参数的隐式传递。 1

在服务消费方端设置隐式参数

setAttachment 设置的 KV 对,在完成下面一次远程调用会被清空,即多次远程调用要多次设置。

代码语言:javascript复制
RpcContext.getContext().setAttachment("index", "1"); // 隐式传参,后面的远程调用都会隐式将这些参数发送到服务器端,类似cookie,用于框架集成,不建议常规业务使用
xxxService.xxx(); // 远程调用
// ...

在服务提供方端获取隐式参数

代码语言:javascript复制
public class XxxServiceImpl implements XxxService {

    public void xxx() {
        // 获取客户端隐式传入的参数,用于框架集成,不建议常规业务使用
        String index = RpcContext.getContext().getAttachment("index"); 
    }
}

  1. 注意:path, group, version, dubbo, token, timeout 几个 key 是保留字段,请使用其它值。 ↩

异步调用

基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。 1

在 consumer.xml 中配置:

代码语言:javascript复制
<dubbo:reference id="fooService" interface="com.alibaba.foo.FooService">
      <dubbo:method name="findFoo" async="true" />
</dubbo:reference>
<dubbo:reference id="barService" interface="com.alibaba.bar.BarService">
      <dubbo:method name="findBar" async="true" />
</dubbo:reference>

调用代码:

代码语言:javascript复制
// 此调用会立即返回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" 不等待消息发出,将消息放入 IO 队列,即刻返回。
代码语言:javascript复制
<dubbo:method name="findFoo" async="true" sent="true" />

如果你只是想异步,完全忽略返回值,可以配置 return="false",以减少 Future 对象的创建和管理成本:

代码语言:javascript复制
<dubbo:method name="findFoo" async="true" return="false" />

  1. 2.0.6 及其以上版本支持 ↩
  2. 异步总是不等待返回 ↩

本地存根

  • dubbo的本地存根的原理是:远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,那么就在服务消费者这一端提供了一个Stub类,然后当消费者调用provider方提供的dubbo服务时,客户端生成 Proxy 实例,这个Proxy实例就是我们正常调用dubbo远程服务要生成的代理实例,然后消费者这方会把 Proxy 通过构造函数传给 消费者方的Stub ,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。会通过代理类去完成这个调用,这样在Stub类中,就可以做一些额外的事,来对服务的调用过程进行优化或者容错的处理。附图:
  • 总结:如果消费者想用在调用远程服务的同时还想在之前或者之后实现自己的部分逻辑,那么就需要在消费者端定义一个代理类,其实在消费者调用服务的时候,实际上是调用的代理类。不过其中代理类返回的数据是可以传递给服务提供者的

实现步骤

1. 定义一个服务接口和服务实现类

代码语言:javascript复制
public interface UserInterface {
    
    
       public User getUserById(Integer id) ;

}
代码语言:javascript复制
public class UserService implements UserInterface {

    public User getUserById(Integer id) {
        User user  = new User() ;
        user.setId(id);
        user.setName("hu");
        return user;
    }  

}
  1. 服务分布配置
代码语言:javascript复制
<dubbo:service  interface="org.huxin.dubbo.test.user.service.UserInterface" ref="userService" protocol="dubbo"    retries="0"/>
        

    <bean id="userService" class="org.huxin.dubbo.test.user.service.impl.UserService" />

3.服务消费者的Stub类

代码语言:javascript复制
public class UserServiceStub implements 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 ;
    }
}
  1. 服务消费方的配置
代码语言:javascript复制
<dubbo:reference id="userService" interface="org.huxin.dubbo.test.user.service.UserInterface" 
                   stub="org.huxin.dubbo.test.UserServiceStub" protocol="dubbo"/>

5.测试代码

代码语言:javascript复制
@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对象也是这个这个方法返回的

本地伪装

  • 本地伪装

延迟暴露

  • 延迟暴露

并发控制

  • 并发控制

连接控制

  • 限制连接个数
  • 连接控制

延迟连接

  • 延迟连接

粘滞连接

粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。

粘滞连接将自动开启延迟连接,以减少长连接数。

代码语言:javascript复制
<dubbo:protocol name="dubbo" sticky="true" />

0 人点赞