dubbo源码愫读(2)dubbo配置分析2

2、属性配置说明

2.1、【cluster】属性配置说明

cluster属性定义服务调用者在调用服务失败后的处理策略。

failover(默认):

  • 失败自动切换,当出现失败,重试其他服务器。
  • 通常用于读操作,但重试会带来更长延迟。
  • 可通过retries=x来设置重试次数(不含第一次)。

failfast:

  • 快速失败,只发起一次调用,失败理解报错。
  • 通常用于非幂等性的写操作,比如新增记录。

failsafe:

  • 失败安全,出现异常时,直接忽略;
  • 通常用于写入审计日志等操作。

failback:

  • 失败自动回复,后台记录失败请求,定时重发。
  • 通常用于消息通知操作。

forking:

  • 并行调用多个服务器,只要一个成功即返回。
  • 通常用于实时性要求较高的读操作,但需要浪费更多服务资源。
  • 可通过forks=x来设置最大并行数。

broadcast:

  • 广播调用所有提供者,逐个调用,任意一台报错则报错。
  • 通常用于通知所有提供者更新缓存或日志等本地资源信息。

2.2、【loadbalance】属性配置说明

loadbalance属性定义服务调用者在调用服务时选取服务提供者的负载均衡策略。

random:

  • 随机,按权重设置随机概率。
  • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

roundrobin:

  • 轮询,按公约后的权重设置轮询比率。
  • 存在慢的提供者累计请求问题,比如:第二台机器很慢,但没挂,当请求调用到第二台是就卡在那,久而久之,所有请求都卡在掉第二台上。

leastactive:

  • 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
  • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

consistenthash:

  • 一致性Hash,相同参数的请求总是发到同一提供者。
  • 当某一台提供者挂掉时,原本发往该提供者的请求,基于虚拟节点,平摊到其他提供者,不会引起剧烈变动。
  • 算法参见:https://en.wikipedia.org/wiki/Consistent_hashiing
  • 缺省只对第一个参考Hash,如果要修改,请配置<dubbo:parameter key="hash.arguments" value="0,1" />
  • 缺省用160份虚拟节点,如果要修改,请配置<dubbo:parameter key="hash.nodes" value="320" />

2.3、【check】属性配置说明

check属性为启动时检查,dubbo缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止Spring初始化完成,以便上线时,能及早发现问题,默认【check=true】。

2.4、线程模型配置说明

事件处理线程说明:

  • 如果事件处理的逻辑能迅速完成,并且不会发起新的IO请求,比如只是在内存中记录一个标记,则直接在IO线程上处理更快,因为减少线程调度。
  • 但如果事件处理逻辑较慢,或者需要发起新的IO请求,比如需要查询数据库,则必须派发到线程池,否则IO线程阻塞,将导致不能接受其他请求。
  • 如果用IO线程处理事件,又在事件处理过程中发起新的IO请求,比如在连接事件中发起登录请求,会报“可能引发死锁”异常,但不会真死锁。

dispatcher属性:

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

threadpool属性:

  • fixed:固定大小线程池,启动时建立线程,不关闭,一直有。(缺省)
  • cached:缓存线程池,空闲一分钟自动删除,需要时重建。
  • limited:可伸缩线程池,但池中的线程数只会增长不会收缩。(为避免收缩是突然来了大流量引起性能问题)。

threads属性:

设置线程池中线程的数量。

2.5、点对点直连

在开发及测试环境下,经常需要绕过注册中心,只测试指定的服务提供者,这时可能需要点对点直连,点对点直连方式,将以服务接口为单位,忽略注册中心的提供者列表,A接口配置点对点,不影响B接口从注册中心获取列表。

  • 如果是线上需要点对点,可在<dubbo:reference>中配置url执行提供者,将绕过注册中心, 多个地址用分号隔开,配置如下:
<dubbo:reference interface="com.alibab.xxx.XxxService" url="dubbo://localhost:20890" />
  • 在JVM启动参数中加入-D参数映射服务地址,如:
java -Dcom.alibaba.xxx.XxxService=dubbo://localhost:2890

key为服务名:com.alibaba.xxx.XxxService;value为服务提供者url,此配置优先级最高。.

  • 如果线上服务比较多,也可用文件映射,如:
java -Ddubbo.resolve.file=xxx.properties

然后再映射文件xxx.properties中加入:

com.alibaba.xxx.XxxService=dubbo://localhost:2890

用-Ddubbo.resolve.file指定映射文件路径,此配置优先级高于<dubbo:reference>中的配置,1.0.15+版本支持,2.0+版本自动加载${user.home}/dubbo-resolve.properties文件,不需要配置。

2.6、只订阅/只注册

只订阅:

为方便开发测试,经常会在线下公用一个所有服务可用的注册中心,这时,如果一个正在开发中的服务提供者注册,可能会影响消费者不能正常运行。

可用让服务提供者开发方,只订阅服务(开发的服务可能依赖其他服务),而不注册正在开发的服务,通过直连测试正在开发的服务。

配置如下:

<dubbo:registry address="192.168.101.100:20800" register=“false” />

或:

<dubbo:registry address="192.168.101.100:20800?register=false” />

只注册:

如果有两个镜像环境,两个注册中心,有一个服务只在其中一个注册中心有部署,另一个注册中心还没来得及部署,而两个注册中心的其他应用都需要依赖此服务,所以需要将服务同时注册到两个注册中心,但却不能让此服务同时依赖两个注册中心的其他服务。

可以让服务提供者方,只注册服务到另一注册中心,而不是另一注册中心订阅服务。

禁用订阅配置:

<dubbo:registry id="test1Registry" address="192.168.101.100:9999" />

<dubbo:registry id="test2Registry" address="192.168.101.111:9999" subscribe="false" />

或者:

<dubbo:registry id="test1Registry" address="192.168.101.100:9999" />

<dubbo:registry id="test2Registry" address="192.168.101.111:9999?subscribe=false" />

<meta charset="utf-8">

2.7、静态服务

有时候希望人工管理服务提供者的上线和下线,此时需将注册中心标识为非动态管理模式。

<dubbo:registry address="192.168.101.100:9999" dynamic="false" />

或者:

<dubbo:registry address="192.168.101.100:9999?dynamic=false" />

服务提供者初次注册是为禁用状态,需人工启用,断线时,将不会被自动删除,需人工禁用。

2.8、多协议

(1)不同服务不同协议:

如:不同服务在性能上适用不同协议进行传输,比如大数据用短连接协议,小数据大并发用长连接协议。

<meta charset="utf-8">

<?xmlversion="1.0"encoding="UTF-8"?>

<beansxmlns=" [http://www.springframework.org/schema/beans"](http://www.springframework.org/schema/beans)

    xmlns:xsi=" [http://www.w3.org/2001/XMLSchema-instance"](http://www.w3.org/2001/XMLSchema-instance)

    xmlns:dubbo=" [http://code.alibabatech.com/schema/dubbo"](http://code.alibabatech.com/schema/dubbo)

    xsi:schemaLocation=" [http://www.springframework.org/schema/beans](http://www.springframework.org/schema/beans)

         [http://www.springframework.org/schema/beans/spring-beans.xsd](http://www.springframework.org/schema/beans/spring-beans.xsd)

         [http://code.alibabatech.com/schema/dubbo](http://code.alibabatech.com/schema/dubbo)

         [http://code.alibabatech.com/schema/dubbo/dubbo.xsd](http://code.alibabatech.com/schema/dubbo/dubbo.xsd)

        ">

    <dubbo:applicationname="world" />

    <dubbo:registryid="registry"address="10.20.141.150:9090"username="admin"password="hello1234"/>

    <!-- 多协议配置 -->

    <dubbo:protocolname="dubbo"port="20880"/>

    <dubbo:protocolname="rmi"port="1099"/>

    <!-- 使用dubbo协议暴露服务 -->

    <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"/>

</beans>


(2)多协议暴露服务

如:需要与http客户端操作

<meta charset="utf-8">

<?xmlversion="1.0"encoding="UTF-8"?>

<beansxmlns=" [http://www.springframework.org/schema/beans"](http://www.springframework.org/schema/beans)

    xmlns:xsi=" [http://www.w3.org/2001/XMLSchema-instance"](http://www.w3.org/2001/XMLSchema-instance)

    xmlns:dubbo=" [http://code.alibabatech.com/schema/dubbo"](http://code.alibabatech.com/schema/dubbo)

    xsi:schemaLocation=" [http://www.springframework.org/schema/beans](http://www.springframework.org/schema/beans)

         [http://www.springframework.org/schema/beans/spring-beans.xsd](http://www.springframework.org/schema/beans/spring-beans.xsd)

         [http://code.alibabatech.com/schema/dubbo](http://code.alibabatech.com/schema/dubbo)

         [http://code.alibabatech.com/schema/dubbo/dubbo.xsd](http://code.alibabatech.com/schema/dubbo/dubbo.xsd)

        ">

    <dubbo:applicationname="world" />

    <dubbo:registryid="registry"address="10.20.141.150:9090"username="admin"password="hello1234"/>

    <!-- 多协议配置 -->

    <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"/>

</beans>

2.9、多注册中心

(1) 多注册中心注册

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

<meta charset="utf-8">

<?xmlversion="1.0"encoding="UTF-8"?>

<beansxmlns=" [http://www.springframework.org/schema/beans"](http://www.springframework.org/schema/beans)

    xmlns:xsi=" [http://www.w3.org/2001/XMLSchema-instance"](http://www.w3.org/2001/XMLSchema-instance)

    xmlns:dubbo=" [http://code.alibabatech.com/schema/dubbo"](http://code.alibabatech.com/schema/dubbo)

    xsi:schemaLocation=" [http://www.springframework.org/schema/beans](http://www.springframework.org/schema/beans)

         [http://www.springframework.org/schema/beans/spring-beans.xsd](http://www.springframework.org/schema/beans/spring-beans.xsd)

         [http://code.alibabatech.com/schema/dubbo](http://code.alibabatech.com/schema/dubbo)

         [http://code.alibabatech.com/schema/dubbo/dubbo.xsd](http://code.alibabatech.com/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>

(2) 不同服务使用不同注册中心

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

<meta charset="utf-8">

<?xmlversion="1.0"encoding="UTF-8"?>

<beansxmlns=" [http://www.springframework.org/schema/beans"](http://www.springframework.org/schema/beans)

    xmlns:xsi=" [http://www.w3.org/2001/XMLSchema-instance"](http://www.w3.org/2001/XMLSchema-instance)

    xmlns:dubbo=" [http://code.alibabatech.com/schema/dubbo"](http://code.alibabatech.com/schema/dubbo)

    xsi:schemaLocation=" [http://www.springframework.org/schema/beans](http://www.springframework.org/schema/beans)

         [http://www.springframework.org/schema/beans/spring-beans.xsd](http://www.springframework.org/schema/beans/spring-beans.xsd)

         [http://code.alibabatech.com/schema/dubbo](http://code.alibabatech.com/schema/dubbo)

         [http://code.alibabatech.com/schema/dubbo/dubbo.xsd](http://code.alibabatech.com/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>

(3) 多注册中心引用

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

<meta charset="utf-8">

<?xmlversion="1.0"encoding="UTF-8"?>

<beansxmlns=" [http://www.springframework.org/schema/beans"](http://www.springframework.org/schema/beans)

    xmlns:xsi=" [http://www.w3.org/2001/XMLSchema-instance"](http://www.w3.org/2001/XMLSchema-instance)

    xmlns:dubbo=" [http://code.alibabatech.com/schema/dubbo"](http://code.alibabatech.com/schema/dubbo)

    xsi:schemaLocation=" [http://www.springframework.org/schema/beans](http://www.springframework.org/schema/beans)

         [http://www.springframework.org/schema/beans/spring-beans.xsd](http://www.springframework.org/schema/beans/spring-beans.xsd)

         [http://code.alibabatech.com/schema/dubbo](http://code.alibabatech.com/schema/dubbo)

         [http://code.alibabatech.com/schema/dubbo/dubbo.xsd](http://code.alibabatech.com/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>

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

<meta charset="utf-8">

<?xmlversion="1.0"encoding="UTF-8"?>

<beansxmlns=" [http://www.springframework.org/schema/beans"](http://www.springframework.org/schema/beans)

    xmlns:xsi=" [http://www.w3.org/2001/XMLSchema-instance"](http://www.w3.org/2001/XMLSchema-instance)

    xmlns:dubbo=" [http://code.alibabatech.com/schema/dubbo"](http://code.alibabatech.com/schema/dubbo)

    xsi:schemaLocation=" [http://www.springframework.org/schema/beans](http://www.springframework.org/schema/beans)

         [http://www.springframework.org/schema/beans/spring-beans.xsd](http://www.springframework.org/schema/beans/spring-beans.xsd)

         [http://code.alibabatech.com/schema/dubbo](http://code.alibabatech.com/schema/dubbo)

         [http://code.alibabatech.com/schema/dubbo/dubbo.xsd](http://code.alibabatech.com/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>

2.10、服务分组

当一个接口有多种实现时,可以用group区分。

服务提供者配置:

<dubbo:service group="china" interface="com.xxx.IndexService"/>

<dubbo:service group="India" interface="com.xxx.IndexService"/>

服务调用者配置:

<dubbo:reference id="chinaIndexService" group="china" interface="com.xxx.IndexService"/>

<dubbo:reference id="indiaIndexService" group="India" interface="com.xxx.IndexService"/>

任意组:(2.2.0以上版本支持,总是只调一个可用组的实现)

<dubbo:reference id="barService" interface="com.foo.BarService" group="*"/>

2.11、多版本

当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。

  • 在低压力时间段,先升级一半提供者为新版本
  • 再将所有消费者升级为新版本
  • 然后将剩下的一半提供者升级为新版本

服务提供者:

<dubbo:service interface="com.foo.BarService" version="1.0.0"/>

<dubbo:service interface="com.foo.BarService" version="2.0.0"/>

服务消费者:

<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0"/>

<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0"/>

不区分版本:(2.2.0以上版本支持)

<dubbo:referenceid="barService"interface="com.foo.BarService"version="*"/>

2.12、分组聚合

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

配置如:(搜索所有分组)

<dubbo:reference interface="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>

或:(指定合并策略,缺省根据返回值类型自动匹配,如果同一类型有两个合并器时,需指定合并器的名称)参见:[合并结果扩展]

<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>

2.13、结果缓存

结果缓存,用于加速热门数据的访问速度,Dubbo提供声明式缓存,以减少用户加缓存的工作量。

  • lru: 基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。
  • threadlocal: 当前线程缓存,比如一个页面渲染,用到很多portal,每个portal都要去查用户信息,通过线程缓存,可以减少这种多余访问。
  • jcache:JSR107集成,可以桥接各种缓存实现。

配置如下:

<dubbo:referenceinterface="com.foo.BarService"cache="lru"/>

或:

<dubbo:referenceinterface="com.foo.BarService">

    <dubbo:methodname="findBar"cache="lru"/>

</dubbo:reference>

2.14、上下文信息

上下文中存放的是当前调用过程中所需的环境信息。所有配置信息都将转换为URL的参数。

RpcContext是一个ThreadLocal的临时状态记录器,当接收到RPC请求,或发起RPC请求时,RpcContext的状态都会变化。比如:A调B,B再调C,则B机器上,在B调C之前,RpcContext记录的是A调B的信息,在B调C之后,RpcContext记录的是B调C的信息。

(1) 服务消费方:

xxxService.xxx();// 远程调用

boolean isConsumerSide = RpcContext.getContext().isConsumerSide();// 本端是否为消费端,这里会返回true

String serverIP = RpcContext.getContext().getRemoteHost();// 获取最后一次调用的提供方IP地址

String application = RpcContext.getContext().getUrl().getParameter("application");// 获取当前服务配置信息,所有配置信息都将转换为URL的参数

// ...

yyyService.yyy();// 注意:每发起RPC调用,上下文状态会变化

// ...

(2) 服务提供方

public class XxxService Implimplements XxxService {

    public void xxx() {// 服务方法实现

        boolean isProviderSide = RpcContext.getContext().isProviderSide();// 本端是否为提供端,这里会返回true

        String clientIP = RpcContext.getContext().getRemoteHost();// 获取调用方IP地址

        String application = RpcContext.getContext().getUrl().getParameter("application");// 获取当前服务配置信息,所有配置信息都将转换为URL的参数

        // ...

        yyyService.yyy();// 注意:每发起RPC调用,上下文状态会变化

        booleanisProviderSide = RpcContext.getContext().isProviderSide();// 此时本端变成消费端,这里会返回false

        // ...

    }

}

2.15、隐试传参

注:path,group,version,dubbo,token,timeout几个key有特殊处理,请使用其它key值。

(1) 服务消费方:

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

xxxService.xxx();// 远程调用

// ...

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

(2) 服务提供方:

public class XxxService Implimplements XxxService {

    publicvoidxxx() {// 服务方法实现

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

        // ...

    }

}

2.16、异步调用

基于NIO的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。(2.0.6及其以上版本支持)

配置声明:

consumer.xml

<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>

调用代码:

fooService.findFoo(fooId);// 此调用会立即返回null

Future<Foo> fooFuture = RpcContext.getContext().getFuture();// 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future。

barService.findBar(barId);// 此调用会立即返回null

Future<Bar> barFuture = RpcContext.getContext().getFuture();// 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future。

// 此时findFoo和findBar的请求同时在执行,客户端不需要启动多线程来支持并行,而是借助NIO的非阻塞完成。

Foo foo = fooFuture.get();// 如果foo已返回,直接拿到返回值,否则线程wait住,等待foo返回后,线程会被notify唤醒。

Bar bar = barFuture.get();// 同理等待bar返回。

// 如果foo需要5秒返回,bar需要6秒返回,实际只需等6秒,即可获取到foo和bar,进行接下来的处理。

你也可以设置是否等待消息发出:(异步总是不等待返回)

  • sent="true" :等待消息发出,消息发送失败将抛出异常。
  • sent="false" :不等待消息发出,将消息放入IO队列,即刻返回。
<dubbo:methodname="findFoo"async="true"sent="true"/>

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

<dubbo:methodname="findFoo"async="true"return="false"/>

2.17、事件通知

在调用之前,调用之后,出现异常时,会触发oninvoke, onreturn, onthrow三个事件,可以配置当事件发生时,通知哪个类的哪个方法。(支持版本:2.0.7之后)

(1) 服务提供者与消费者共享服务接口:

IDemoService.java

interfaceIDemoService {
    publicPerson get(intid);
}

(2) 服务提供者实现:

DemoServiceImpl.java

classNormalDemoServiceimplementsIDemoService {

    publicPerson get(intid) {
        returnnewPerson(id,"charles`son",4);
    }
}

(3) 服务提供者配置:

provider.xml

<dubbo:applicationname="rpc-callback-demo"/>

<dubbo:registryaddress="10.20.153.186"/>

<beanid="demoService"class="com.alibaba.dubbo.callback.implicit.NormalDemoService"/>

<dubbo:serviceinterface="com.alibaba.dubbo.callback.implicit.IDemoService"ref="demoService"version="1.0.0"group="cn"/>

(4) 服务消费者Callback接口及实现:

Nofify.java

interface Nofify {
    public void onreturn(Person msg, Integer id);
    public void onthrow(Throwable ex, Integer id);
}

NofifyImpl.java

class NofifyImpl implements Nofify {

    publicMap<Integer, Person>    ret    =newHashMap<Integer, Person>();
    publicMap<Integer, Throwable> errors =newHashMap<Integer, Throwable>();
    public void onreturn(Person msg, Integer id) {
        System.out.println("onreturn:"+ msg);
        ret.put(id, msg);
    }

    public void onthrow(Throwable ex, Integer id) {
        errors.put(id, ex);
    }
}

(5) 服务消费者Callback接口及实现:

consumer.xml

<beanid="demoCallback"class="com.alibaba.dubbo.callback.implicit.NofifyImpl"/>

<dubbo:referenceid="demoService"interface="com.alibaba.dubbo.callback.implicit.IDemoService"version="1.0.0"group="cn">

      <dubbo:methodname="get"async="true"onreturn="demoCallback.onreturn"onthrow="demoCallback.onthrow"/>

</dubbo:reference>

注:

callback与async功能正交分解:
async=true,表示结果是否马上返回.
onreturn 表示是否需要回调.

  • 组合情况:(async=false 默认)
  • 异步回调模式:async=true onreturn="xxx"
  • 同步回调模式:async=false onreturn="xxx"
  • 异步无回调 :async=true
  • 同步无回调 :async=false

(6) TEST CASE:

Test.java

IDemoService demoService = (IDemoService) context.getBean("demoService");
NofifyImpl notify = (NofifyImpl) context.getBean("demoCallback");
intrequestId =2;
Person ret = demoService.get(requestId);
Assert.assertEquals(null, ret);
//for Test:只是用来说明callback正常被调用,业务具体实现自行决定.
for(inti =0; i <10; i++) {
    if(!notify.ret.containsKey(requestId)) {
        Thread.sleep(200);
    }else{
        break;
    }
}
Assert.assertEquals(requestId, notify.ret.get(requestId).getId());

2.18、本地存根

远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做ThreadLocal缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在API中带上Stub,客户端生成Proxy实,会把Proxy通过构造函数传给Stub,然后把Stub暴露组给用户,Stub可以决定要不要去调Proxy。Stub必须有可传入Proxy的构造函数。

<dubbo:service interface="com.foo.BarService" stub="true"/>

或:

<dubbo:service interface="com.foo.BarService" stub="com.foo.BarServiceStub"/>

api.jar:

com.foo.BarService

com.foo.BarServiceStub // 在API旁边放一个Stub实现,它实现BarService接口,并有一个传入远程BarService实例的构造函数
packagecom.foo
publicclassBarServiceStubimplementsBarService {
 
    privatefinalBarService barService;
 
    // 构造函数传入真正的远程代理对象
    public(BarService barService) {
        this.barService = barService;
    }
 
    publicString sayHello(String name) {
        // 此代码在客户端执行
        // 你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等
        try{
            returnbarService.sayHello(name);
        }catch(Exception e) {
            // 你可以容错,可以做任何AOP拦截事项
            return"容错数据";
        }
    }
}

2.19、本地伪装

Mock通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过Mock数据返回授权失败。

Mock是Stub的一个子集,便于服务提供方在客户端执行容错逻辑,因经常需要在出现RpcException(比如网络失败,超时等)时进行容错,而在出现业务异常(比如登录用户名密码错误)时不需要容错,如果用Stub,可能就需要捕获并依赖RpcException类,而用Mock就可以不依赖RpcException,因为它的约定就是只有出现RpcException时才执行。

<dubbo:service interface="com.foo.BarService" mock="true"/>

或:

<dubbo:service interface="com.foo.BarService" mock="com.foo.BarServiceMock"/>

api.jar:

com.foo.BarService

com.foo.BarServiceMock // 在API旁边放一个Mock实现,它实现BarService接口,并有一个无参构造函数
packagecom.foo
publicclassBarServiceMockimplementsBarService {
 
    publicString sayHello(String name) {
        // 你可以伪造容错数据,此方法只在出现RpcException时被执行
        return"容错数据";
    }
}

如果服务的消费方经常需要try-catch捕获异常,如:

Offer offer =null;
try{
    offer = offerService.findOffer(offerId);
}catch(RpcException e) {
   logger.error(e);
}

请考虑改为Mock实现,并在Mock中return null。

如果只是想简单的忽略异常,在2.0.11以上版本可用:

<dubbo:service interface="com.foo.BarService" mock="return null"/>

2.20、延迟暴露

如果你的服务需要Warmup时间,比如初始化缓存,等待相关资源就位等,可以使用delay进行延迟暴露。

延迟5秒暴露服务:

<dubbo:servicedelay="5000"/>

延迟到Spring初始化完成后,再暴露服务:(基于Spring的ContextRefreshedEvent事件触发暴露)

<dubbo:servicedelay="-1"/>

Spring2.x初始化死锁问题

在Spring解析到<dubbo:service />时,就已经向外暴露了服务,而Spring还在接着初始化其它Bean。如果这时有请求进来,并且服务的实现类里有调用applicationContext.getBean()的用法。

1. 请求线程的applicationContext.getBean()调用,先同步singletonObjects判断Bean是否存在,不存在就同步beanDefinitionMap进行初始化,并再次同步singletonObjects写入Bean实例缓存。

2. 而Spring初始化线程,因不需要判断Bean的存在,直接同步beanDefinitionMap进行初始化,并同步singletonObjects写入Bean实例缓存。

这样就导致getBean线程,先锁singletonObjects,再锁beanDefinitionMap,再次锁singletonObjects。而Spring初始化线程,先锁beanDefinitionMap,再锁singletonObjects。

反向锁导致线程死锁,不能提供服务,启动不了。

规避办法

1. 强烈建议不要在服务的实现类中有applicationContext.getBean()的调用,全部采用IoC注入的方式使用Spring的Bean。

2. 如果实在要调getBean(),可以将Dubbo的配置放在Spring的最后加载。

3. 如果不想依赖配置顺序,可以使用<dubbo:provider deplay=”-1” />,使Dubbo在Spring容器初始化完后,再暴露服务。

4. 如果大量使用getBean(),相当于已经把Spring退化为工厂模式在用,可以将Dubbo的服务隔离单独的Spring容器。

2.21、并发控制

限制com.foo.BarService的每个方法,服务器端并发执行(或占用线程池线程数)不能超过10个:

<dubbo:service interface="com.foo.BarService" executes="10"/>

限制com.foo.BarService的sayHello方法,服务器端并发执行(或占用线程池线程数)不能超过10个:

<dubbo:service interface="com.foo.BarService">

    <dubbo:method name="sayHello" executes="10"/>

</dubbo:service>

限制com.foo.BarService的每个方法,每客户端并发执行(或占用连接的请求数)不能超过10个:

<dubbo:service interface="com.foo.BarService" actives="10"/>

或:

<dubbo:reference interface="com.foo.BarService" actives="10"/>

限制com.foo.BarService的sayHello方法,每客户端并发执行(或占用连接的请求数)不能超过10个:

<dubbo:service interface="com.foo.BarService">

    <dubbo:method name="sayHello" actives="10"/>

</dubbo:service>

或:

<dubbo:referenceinterface="com.foo.BarService">

    <dubbo:methodname="sayHello"actives="10"/>

</dubbo:service>

如果<dubbo:service>和<dubbo:reference>都配了actives,<dubbo:reference>优先。

配置服务的客户端的loadbalance属性为leastactive,此Loadbalance会调用并发数最小的Provider(Consumer端并发数)。

<dubbo:referenceinterface="com.foo.BarService"loadbalance="leastactive"/>

或:

<dubbo:serviceinterface="com.foo.BarService"loadbalance="leastactive"/>

2.22、连接控制

限制服务器端接受的连接不能超过10个:(以连接在Server上,所以配置在Provider上)

<dubbo:provider protocol="dubbo" accepts="10"/>

<dubbo:protocol name="dubbo" accepts="10"/>

限制客户端服务使用连接连接数:(如果是长连接,比如Dubbo协议,connections表示该服务对每个提供者建立的长连接数)

<dubbo:referenceinterface="com.foo.BarService"connections="10"/>

或:

<dubbo:serviceinterface="com.foo.BarService"connections="10"/>

如果<dubbo:service>和<dubbo:reference>都配了connections,<dubbo:reference>优先。

2.23、延迟连接

延迟连接,用于减少长连接数,当有调用发起时,再创建长连接。只对使用长连接的dubbo协议生效。

<dubbo:protocolname="dubbo"lazy="true"/>

2.24、粘滞连接

粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。粘滞连接将自动开启延迟连接,以减少长连接数。

<dubbo:protocolname="dubbo"sticky="true"/>

2.25、令牌验证

令牌验证的作用:

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

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

<!--随机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:protocolname="dubbo"token="true"/>

<!--固定token令牌,相当于密码-->

<dubbo:protocolname="dubbo"token="123456"/>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容