dubbo高级配置学习
启动时检查Dubbo缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止Spring初始化完成,以便上线时,能及早发现问题,默认check=true。如果你的Spring容器是懒加载的,或者通过API编程延迟引用服务,请关闭check,否则服务临时不可用时,会抛出异常,拿到null引用,如果check=fa
启动时检查
可以通过check="false"关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。
关闭某个服务的启动时检查:(没有提供者时报错)
<
dubbo:reference
interface
=
"com.foo.BarService"
check
=
"false"
/>
|
关闭所有服务的启动时检查:(没有提供者时报错)
<
dubbo:consumer
check
=
"false"
/>
|
关闭注册中心启动时检查:(注册订阅失败时报错)
<
dubbo:registry
check
=
"false"
/>
|
也可以用dubbo.properties配置:
dubbo.reference.com.foo.BarService.check=false
dubbo.reference.check=false
dubbo.consumer.check=false
dubbo.registry.check=false
|
也可以用-D参数:
java -Ddubbo.reference.com.foo.BarService.check=false
java -Ddubbo.reference.check=false
java -Ddubbo.consumer.check=false
java -Ddubbo.registry.check=false
|
引用缺省是延迟初始化的,只有引用被注入到其它Bean,或被getBean()获取,才会初始化。
如果需要饥饿加载,即没有人引用也立即生成动态代理,可以配置:
<
dubbo:reference
interface
=
"com.foo.BarService"
init
=
"true"
/>
|
集群容错
各节点关系:
- 这里的Invoker是Provider的一个可调用Service的抽象,Invoker封装了Provider地址及Service接口信息。
- Directory代表多个Invoker,可以把它看成List<Invoker>,但与List不同的是,它的值可能是动态变化的,比如注册中心推送变更。
- Cluster将Directory中的多个Invoker伪装成一个Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个。
- Router负责从多个Invoker中按路由规则选出子集,比如读写分离,应用隔离等。
- LoadBalance负责从多个Invoker中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选。
集群容错模式:
可以自行扩展集群容错策略,参见:集群扩展
Failover Cluster
- 失败自动切换,当出现失败,重试其它服务器。(缺省)
- 通常用于读操作,但重试会带来更长延迟。
- 可通过retries="2"来设置重试次数(不含第一次)。
Failfast Cluster
- 快速失败,只发起一次调用,失败立即报错。
- 通常用于非幂等性的写操作,比如新增记录。
Failsafe Cluster
- 失败安全,出现异常时,直接忽略。
- 通常用于写入审计日志等操作。
Failback Cluster
- 失败自动恢复,后台记录失败请求,定时重发。
- 通常用于消息通知操作。
Forking Cluster
- 并行调用多个服务器,只要一个成功即返回。
- 通常用于实时性要求较高的读操作,但需要浪费更多服务资源。
- 可通过forks="2"来设置最大并行数。
Broadcast Cluster
- 广播调用所有提供者,逐个调用,任意一台报错则报错。(2.1.0开始支持)
- 通常用于通知所有提供者更新缓存或日志等本地资源信息。
重试次数配置如:(failover集群模式生效)
<
dubbo:service
retries
=
"2"
/>
|
或:
<
dubbo:reference
retries
=
"2"
/>
|
或:
<
dubbo:reference
>
<
dubbo:method
name
=
"findFoo"
retries
=
"2"
/>
</
dubbo:reference
>
|
集群模式配置如:
<
dubbo:service
cluster
=
"failsafe"
/>
|
或:
<
dubbo:reference
cluster
=
"failsafe"
/>
|
负载均衡
可以自行扩展负载均衡策略,参见:负载均衡扩展
Random LoadBalance
- 随机,按权重设置随机概率。
- 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
RoundRobin LoadBalance
- 轮循,按公约后的权重设置轮循比率。
- 存在慢的提供者累积请求问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
LeastActive LoadBalance
- 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
- 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
ConsistentHash LoadBalance
- 一致性Hash,相同参数的请求总是发到同一提供者。
- 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
- 算法参见:http://en.wikipedia.org/wiki/Consistent_hashing。
- 缺省只对第一个参数Hash,如果要修改,请配置<dubbo:parameter key="hash.arguments" value="0,1" />
- 缺省用160份虚拟节点,如果要修改,请配置<dubbo:parameter key="hash.nodes" value="320" />
配置如:
<
dubbo:service
interface
=
"..."
loadbalance
=
"roundrobin"
/>
|
或:
<
dubbo:reference
interface
=
"..."
loadbalance
=
"roundrobin"
/>
|
或:
<
dubbo:service
interface
=
"..."
>
<
dubbo:method
name
=
"..."
loadbalance
=
"roundrobin"
/>
</
dubbo:service
>
|
或:
<
dubbo:reference
interface
=
"..."
>
<
dubbo:method
name
=
"..."
loadbalance
=
"roundrobin"
/>
</
dubbo:reference
>
|
线程模型
- Dispatcher
- all 所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。
- direct 所有消息都不派发到线程池,全部在IO线程上直接执行。
- message 只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在IO线程上执行。
- execution 只请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在IO线程上执行。
- connection 在IO线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。
- ThreadPool
- fixed 固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)
- cached 缓存线程池,空闲一分钟自动删除,需要时重建。
- limited 可伸缩线程池,但池中的线程数只会增长不会收缩。(为避免收缩时突然来了大流量引起的性能问题)。
配置如:
<
dubbo:protocol
name
=
"dubbo"
dispatcher
=
"all"
threadpool
=
"fixed"
threads
=
"100"
/>
|
直连提供者
在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,
点对点直联方式,将以服务接口为单位,忽略注册中心的提供者列表,
A接口配置点对点,不影响B接口从注册中心获取列表。
(1) 如果是线上需求需要点对点,可在<dubbo:reference>中配置url指向提供者,将绕过注册中心,多个地址用分号隔开,配置如下:(1.0.6及以上版本支持)
<dubbo:reference id=
"xxxService"
interface
=
"com.alibaba.xxx.XxxService"
url=
"dubbo://localhost:20890"
/>
|
(2) 在JVM启动参数中加入-D参数映射服务地址,如:
(key为服务名,value为服务提供者url,此配置优先级最高,1.0.15及以上版本支持)
java -Dcom.alibaba.xxx.XxxService=dubbo:
//localhost:20890
|
(3) 如果服务比较多,也可以用文件映射,如:
(用-Ddubbo.resolve.file指定映射文件路径,此配置优先级高于<dubbo:reference>中的配置,1.0.15及以上版本支持)
(2.0以上版本自动加载${user.home}/dubbo-resolve.properties文件,不需要配置)
java -Ddubbo.resolve.file=xxx.properties
|
然后在映射文件xxx.properties中加入:
(key为服务名,value为服务提供者url)
com.alibaba.xxx.XxxService=dubbo:
//localhost:20890
|
只订阅
禁用注册配置:
<
dubbo:registry
address
=
"10.20.153.10:9090"
register
=
"false"
/>
|
或者:
<
dubbo:registry
address
=
"10.20.153.10:9090?register=false"
/>
|
只注册
禁用订阅配置:
<
dubbo:registry
id
=
"hzRegistry"
address
=
"10.20.153.10:9090"
/>
<
dubbo:registry
id
=
"qdRegistry"
address
=
"10.20.141.150:9090"
subscribe
=
"false"
/>
|
或者:
<
dubbo:registry
id
=
"hzRegistry"
address
=
"10.20.153.10:9090"
/>
<
dubbo:registry
id
=
"qdRegistry"
address
=
"10.20.141.150:9090?subscribe=false"
/>
|
静态服务
<
dubbo:registry
address
=
"10.20.141.150:9090"
dynamic
=
"false"
/>
|
或者:
<
dubbo:registry
address
=
"10.20.141.150:9090?dynamic=false"
/>
|
服务提供者初次注册时为禁用状态,需人工启用,断线时,将不会被自动删除,需人工禁用。
如果是一个第三方独立提供者,比如memcached等,可以直接向注册中心写入提供者地址信息,消费者正常使用:
(通常由脚本监控中心页面等调用)
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.
class
).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf(
"zookeeper://10.20.153.10:2181"
));
registry.register(URL.valueOf(
"memcached://10.20.153.11/com.foo.BarService?category=providers&dynamic=false&application=foo"
));
|
多协议
可以自行扩展协议,参见:协议扩展
(1) 不同服务不同协议
比如:不同服务在性能上适用不同协议进行传输,比如大数据用短连接协议,小数据大并发用长连接协议。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="world" />
<dubbo:registry id="registry" address="10.20.141.150:9090" username="admin" password="hello1234" />
<!-- 多协议配置 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="rmi" port="1099" />
<!-- 使用dubbo协议暴露服务 -->
<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" />
</beans>
|
(2) 多协议暴露服务
比如:需要与http客户端互操作
<?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://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="world" />
<dubbo:registry id="registry" address="10.20.141.150:9090" username="admin" password="hello1234" />
<!-- 多协议配置 -->
<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" />
</beans>
|
多注册中心
可以自行扩展注册中心,参见:注册中心扩展
(1) 多注册中心注册
比如:中文站有些服务来不及在青岛部署,只在杭州部署,而青岛的其它应用需要引用此服务,就可以将服务同时注册到两个注册中心。
<?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://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/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>
|
(2) 不同服务使用不同注册中心
比如:CRM有些服务是专门为国际站设计的,有些服务是专门为中文站设计的。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/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>
|
(3) 多注册中心引用
比如:CRM需同时调用中文站和国际站的PC2服务,PC2在中文站和国际站均有部署,接口及版本号都一样,但连的数据库不一样。
<?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://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/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>
|
如果只是测试环境临时需要连接两个不同注册中心,使用竖号分隔多个不同注册中心地址:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/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>
|
服务分组
<
dubbo:service
group
=
"feedback"
interface
=
"com.xxx.IndexService"
/>
<
dubbo:service
group
=
"member"
interface
=
"com.xxx.IndexService"
/>
|
<
dubbo:reference
id
=
"feedbackIndexService"
group
=
"feedback"
interface
=
"com.xxx.IndexService"
/>
<
dubbo:reference
id
=
"memberIndexService"
group
=
"member"
interface
=
"com.xxx.IndexService"
/>
|
任意组:(2.2.0以上版本支持,总是只调一个可用组的实现)
<
dubbo:reference
id
=
"barService"
interface
=
"com.foo.BarService"
group
=
"*"
/>
|
多版本
- 在低压力时间段,先升级一半提供者为新版本
- 再将所有消费者升级为新版本
- 然后将剩下的一半提供者升级为新版本
<
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:reference
id
=
"barService"
interface
=
"com.foo.BarService"
version
=
"*"
/>
|
分组聚合
配置如:(搜索所有分组)
<
dubbo:reference
interface
=
"com.xxx.MenuService"
group
=
"*"
merger
=
"true"
/>
|
或:(合并指定分组)
<
dubbo:reference
interface
=
"com.xxx.MenuService"
group
=
"aaa,bbb"
merger
=
"true"
/>
|
或:(指定方法合并结果,其它未指定的方法,将只调用一个Group)
<
dubbo:reference
interface
=
"com.xxx.MenuService"
group
=
"*"
>
<
dubbo:method
name
=
"getMenuItems"
merger
=
"true"
/>
</
dubbo:service
>
|
或:(某个方法不合并结果,其它都合并结果)
<
dubbo:reference
interface
=
"com.xxx.MenuService"
group
=
"*"
merger
=
"true"
>
<
dubbo:method
name
=
"getMenuItems"
merger
=
"false"
/>
</
dubbo:service
>
|
或:(指定合并策略,缺省根据返回值类型自动匹配,如果同一类型有两个合并器时,需指定合并器的名称)
参见:[合并结果扩展]
<
dubbo:reference
interface
=
"com.xxx.MenuService"
group
=
"*"
>
<
dubbo:method
name
=
"getMenuItems"
merger
=
"mymerge"
/>
</
dubbo:service
>
|
或:(指定合并方法,将调用返回结果的指定方法进行合并,合并方法的参数类型必须是返回结果类型本身)
<
dubbo:reference
interface
=
"com.xxx.MenuService"
group
=
"*"
>
<
dubbo:method
name
=
"getMenuItems"
merger
=
".addAll"
/>
</
dubbo:service
>
|
参数验证
验证方式可扩展,参见:Validation扩展点
参数标注示例:
import
java.io.Serializable;
import
java.util.Date;
import
javax.validation.constraints.Future;
import
javax.validation.constraints.Max;
import
javax.validation.constraints.Min;
import
javax.validation.constraints.NotNull;
import
javax.validation.constraints.Past;
import
javax.validation.constraints.Pattern;
import
javax.validation.constraints.Size;
public
class
ValidationParameter
implements
Serializable {
private
static
final
long
serialVersionUID = 7158911668568000392L;
@NotNull
// 不允许为空
@Size
(min =
1
, max =
20
)
// 长度或大小范围
private
String name;
@NotNull
(groups = ValidationService.Save.
class
)
// 保存时不允许为空,更新时允许为空 ,表示不更新该字段
@Pattern
(regexp =
"^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$"
)
private
String email;
@Min
(
18
)
// 最小值
@Max
(
100
)
// 最大值
private
int
age;
@Past
// 必须为一个过去的时间
private
Date loginDate;
@Future
// 必须为一个未来的时间
private
Date expiryDate;
public
String getName() {
return
name;
}
public
void
setName(String name) {
this
.name = name;
}
public
String getEmail() {
return
email;
}
public
void
setEmail(String email) {
this
.email = email;
}
public
int
getAge() {
return
age;
}
public
void
setAge(
int
age) {
this
.age = age;
}
public
Date getLoginDate() {
return
loginDate;
}
public
void
setLoginDate(Date loginDate) {
this
.loginDate = loginDate;
}
public
Date getExpiryDate() {
return
expiryDate;
}
public
void
setExpiryDate(Date expiryDate) {
this
.expiryDate = expiryDate;
}
}
|
分组验证示例:
public
interface
ValidationService {
// 缺省可按服务接口区分验证场景,如:@NotNull(groups = ValidationService.class)
@interface
Save{}
// 与方法同名接口,首字母大写,用于区分验证场景,如:@NotNull(groups = ValidationService.Save.class),可选
void
save(ValidationParameter parameter);
void
update(ValidationParameter parameter);
}
|
关联验证示例:
import
javax.validation.GroupSequence;
public
interface
ValidationService {
@GroupSequence
(Update.
class
)
// 同时验证Update组规则
@interface
Save{}
void
save(ValidationParameter parameter);
@interface
Update{}
void
update(ValidationParameter parameter);
}
|
参数验证示例:
import
javax.validation.constraints.Min;
import
javax.validation.constraints.NotNull;
public
interface
ValidationService {
void
save(
@NotNull
ValidationParameter parameter);
// 验证参数不为空
void
delete(
@Min
(
1
)
int
id);
// 直接对基本类型参数验证
}
|
在客户端验证参数:
<
dubbo:reference
id
=
"validationService"
interface
=
"com.alibaba.dubbo.examples.validation.api.ValidationService"
validation
=
"true"
/>
|
在服务器端验证参数:
<
dubbo:service
interface
=
"com.alibaba.dubbo.examples.validation.api.ValidationService"
ref
=
"validationService"
validation
=
"true"
/>
|
验证异常信息:
import
javax.validation.ConstraintViolationException;
import
javax.validation.ConstraintViolationException;
import
org.springframework.context.support.ClassPathXmlApplicationContext;
import
com.alibaba.dubbo.examples.validation.api.ValidationParameter;
import
com.alibaba.dubbo.examples.validation.api.ValidationService;
import
com.alibaba.dubbo.rpc.RpcException;
public
class
ValidationConsumer {
public
static
void
main(String[] args)
throws
Exception {
String config = ValidationConsumer.
class
.getPackage().getName().replace(
'.'
,
'/'
) +
"/validation-consumer.xml"
;
ClassPathXmlApplicationContext context =
new
ClassPathXmlApplicationContext(config);
context.start();
ValidationService validationService = (ValidationService)context.getBean(
"validationService"
);
// Error
try
{
parameter =
new
ValidationParameter();
validationService.save(parameter);
System.out.println(
"Validation ERROR"
);
}
catch
(RpcException e) {
// 抛出的是RpcException
ConstraintViolationException ve = (ConstraintViolationException) e.getCause();
// 里面嵌了一个ConstraintViolationException
Set<ConstraintViolation<?>> violations = ve.getConstraintViolations();
// 可以拿到一个验证错误详细信息的集合
System.out.println(violations);
}
}
}
|
需要加入依赖:
<
dependency
>
<
groupId
>javax.validation</
groupId
>
<
artifactId
>validation-api</
artifactId
>
<
version
>1.0.0.GA</
version
>
</
dependency
>
<
dependency
>
<
groupId
>org.hibernate</
groupId
>
<
artifactId
>hibernate-validator</
artifactId
>
<
version
>4.2.0.Final</
version
>
</
dependency
>
|
结果缓存
- lru 基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。
- threadlocal 当前线程缓存,比如一个页面渲染,用到很多portal,每个portal都要去查用户信息,通过线程缓存,可以减少这种多余访问。
- jcache 与JSR107集成,可以桥接各种缓存实现。
缓存类型可扩展,参见:CacheFactory扩展点
配置如:
<
dubbo:reference
interface
=
"com.foo.BarService"
cache
=
"lru"
/>
|
或:
<
dubbo:reference
interface
=
"com.foo.BarService"
>
<
dubbo:method
name
=
"findBar"
cache
=
"lru"
/>
</
dubbo:reference
>
|
泛化引用
<
dubbo:reference
id
=
"barService"
interface
=
"com.foo.BarService"
generic
=
"true"
/>
|
GenericService barService = (GenericService) applicationContext.getBean(
"barService"
);
Object result = barService.$invoke(
"sayHello"
,
new
String[] {
"java.lang.String"
},
new
Object[] {
"World"
});
|
import
com.alibaba.dubbo.rpc.service.GenericService;
...
// 引用远程服务
ReferenceConfig<GenericService> reference =
new
ReferenceConfig<GenericService>();
// 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存
reference.setInterface(
"com.xxx.XxxService"
);
// 弱类型接口名
reference.setVersion(
"1.0.0"
);
reference.setGeneric(
true
);
// 声明为泛化接口
GenericService genericService = reference.get();
// 用com.alibaba.dubbo.rpc.service.GenericService可以替代所有接口引用
// 基本类型以及Date,List,Map等不需要转换,直接调用
Object result = genericService.$invoke(
"sayHello"
,
new
String[] {
"java.lang.String"
},
new
Object[] {
"world"
});
// 用Map表示POJO参数,如果返回值为POJO也将自动转成Map
Map<String, Object> person =
new
HashMap<String, Object>();
person.put(
"name"
,
"xxx"
);
person.put(
"password"
,
"yyy"
);
Object result = genericService.$invoke(
"findPerson"
,
new
String[]{
"com.xxx.Person"
},
new
Object[]{person});
// 如果返回POJO将自动转成Map
...
|
假设存在POJO如:
package
com.xxx;
public
class
PersonImpl
implements
Person {
private
String name;
private
String password;
public
String getName() {
return
name;
}
public
void
setName(String name) {
this
.name = name;
}
public
String getPassword() {
return
password;
}
public
void
setPassword(String password) {
this
.password= password;
}
}
|
则POJO数据:
Person person =
new
PersonImpl();
person.setName(
"xxx"
);
person.setPassword(
"yyy"
);
|
可用下面Map表示:
Map<String, Object> map =
new
HashMap<String, Object>();
map.put(
"class"
,
"com.xxx.PersonImpl"
);
// 注意:如果参数类型是接口,或者List等丢失泛型,可通过class属性指定类型。
map.put(
"name"
,
"xxx"
);
map.put(
"password"
,
"yyy"
);
|
泛化实现
<
bean
id
=
"genericService"
class
=
"com.foo.MyGenericService"
/>
<
dubbo:service
interface
=
"com.foo.BarService"
ref
=
"genericService"
/>
|
package
com.foo;
public
class
MyGenericService
implements
GenericService {
public
Object $invoke(String methodName, String[] parameterTypes, Object[] args)
throws
GenericException {
if
(
"sayHello"
.equals(methodName)) {
return
"Welcome "
+ args[
0
];
}
}
}
|
...
GenericService xxxService =
new
XxxGenericService();
// 用com.alibaba.dubbo.rpc.service.GenericService可以替代所有接口实现
ServiceConfig<GenericService> service =
new
ServiceConfig<GenericService>();
// 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存
service.setInterface(
"com.xxx.XxxService"
);
// 弱类型接口名
service.setVersion(
"1.0.0"
);
service.setRef(xxxService);
// 指向一个通用服务实现
// 暴露及注册服务
service.export();
|
回声测试
<
dubbo:reference
id
=
"memberService"
interface
=
"com.xxx.MemberService"
/>
|
MemberService memberService = ctx.getBean(
"memberService"
);
// 远程服务引用
EchoService echoService = (EchoService) memberService;
// 强制转型为EchoService
String status = echoService.$echo(
"OK"
);
// 回声测试可用性
assert
(status.equals(
"OK"
))
|
上下文信息
(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
XxxServiceImpl
implements
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调用,上下文状态会变化
boolean
isProviderSide = RpcContext.getContext().isProviderSide();
// 此时本端变成消费端,这里会返回false
// ...
}
}
|
隐式传参
(1) 服务消费方
RpcContext.getContext().setAttachment(
"index"
,
"1"
);
// 隐式传参,后面的远程调用都会隐式将这些参数发送到服务器端,类似cookie,用于框架集成,不建议常规业务使用
xxxService.xxx();
// 远程调用
// ...
|
【注】 setAttachment设置的KV,在完成下面一次远程调用会被清空。即多次远程调用要多次设置。
(2) 服务提供方
public
class
XxxServiceImpl
implements
XxxService {
public
void
xxx() {
// 服务方法实现
String index = RpcContext.getContext().getAttachment(
"index"
);
// 获取客户端隐式传入的参数,用于框架集成,不建议常规业务使用
// ...
}
}
|
异步调用
配置声明:
<
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:method
name
=
"findFoo"
async
=
"true"
sent
=
"true"
/>
如果你只是想异步,完全忽略返回值,可以配置return="false",以减少Future对象的创建和管理成本:
<
dubbo:method
name
=
"findFoo"
async
=
"true"
return
=
"false"
/>
|
本地调用
Define injvm protocol:
<
dubbo:protocol
name
=
"injvm"
/>
|
Set default protocol:
<
dubbo:provider
protocol
=
"injvm"
/>
|
Set service protocol:
<
dubbo:service
protocol
=
"injvm"
/>
|
Use injvm first:
<
dubbo:consumer
injvm
=
"true"
.../>
<
dubbo:provider
injvm
=
"true"
.../>
|
或
<
dubbo:reference
injvm
=
"true"
.../>
<
dubbo:service
injvm
=
"true"
.../>
|
从 dubbo 2.2.0 开始,每个服务默认都会在本地暴露;在引用服务的时候,默认优先引用本地服务;如果希望引用远程服务可以使用一下配置强制引用远程服务。
...
<
dubbo:reference
...
scope
=
"remote"
/>
...
|
参数回调
(1) 共享服务接口:
服务接口示例:
package
com.callback;
public
interface
CallbackService {
void
addListener(String key, CallbackListener listener);
}
|
package
com.callback;
public
interface
CallbackListener {
void
changed(String msg);
}
|
(2) 服务提供者:
服务提供者接口实现示例:
package
com.callback.impl;
import
java.text.SimpleDateFormat;
import
java.util.Date;
import
java.util.Map;
import
java.util.concurrent.ConcurrentHashMap;
import
com.callback.CallbackListener;
import
com.callback.CallbackService;
public
class
CallbackServiceImpl
implements
CallbackService {
private
final
Map<String, CallbackListener> listeners =
new
ConcurrentHashMap<String, CallbackListener>();
public
CallbackServiceImpl() {
Thread t =
new
Thread(
new
Runnable() {
public
void
run() {
while
(
true
) {
try
{
for
(Map.Entry<String, CallbackListener> entry : listeners.entrySet()){
try
{
entry.getValue().changed(getChanged(entry.getKey()));
}
catch
(Throwable t) {
listeners.remove(entry.getKey());
}
}
Thread.sleep(
5000
);
// 定时触发变更通知
}
catch
(Throwable t) {
// 防御容错
t.printStackTrace();
}
}
}
});
t.setDaemon(
true
);
t.start();
}
public
void
addListener(String key, CallbackListener listener) {
listeners.put(key, listener);
listener.changed(getChanged(key));
// 发送变更通知
}
private
String getChanged(String key) {
return
"Changed: "
+
new
SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss"
).format(
new
Date());
}
}
|
服务提供者配置示例:
<
bean
id
=
"callbackService"
class
=
"com.callback.impl.CallbackServiceImpl"
/>
<
dubbo:service
interface
=
"com.callback.CallbackService"
ref
=
"callbackService"
connections
=
"1"
callbacks
=
"1000"
>
<
dubbo:method
name
=
"addListener"
>
<
dubbo:argument
index
=
"1"
callback
=
"true"
/>
<!--也可以通过指定类型的方式-->
<!--<dubbo:argument type="com.demo.CallbackListener" callback="true" />-->
</
dubbo:method
>
</
dubbo:service
>
|
(2) 服务消费者:
服务消费者配置示例:
<
dubbo:reference
id
=
"callbackService"
interface
=
"com.callback.CallbackService"
/>
|
服务消费者调用示例:
context.start();
CallbackService callbackService = (CallbackService) context.getBean("callbackService");
callbackService.addListener("http://10.20.160.198/wiki/display/dubbo/foo.bar", new CallbackListener(){
public void changed(String msg) {
System.out.println("callback1:" + msg);
}
});
|
|
事件通知
(1) 服务提供者与消费者共享服务接口:
interface
IDemoService {
public
Person get(
int
id);
}
|
(2) 服务提供者实现:
class
NormalDemoService
implements
IDemoService {
public
Person get(
int
id) {
return
new
Person(id,
"charles`son"
,
4
);
}
}
|
(3) 服务提供者配置:
<dubbo:application name="rpc-callback-demo" /> <dubbo:registry address="http://10.20.160.198/wiki/display/dubbo/10.20.153.186" /> <bean id="demoService" class="com.alibaba.dubbo.callback.implicit.NormalDemoService" /> <dubbo:service interface="com.alibaba.dubbo.callback.implicit.IDemoService" ref="demoService" version="1.0.0" group="cn"/>
|
(4) 服务消费者Callback接口及实现:
interface
Nofify {
public
void
onreturn(Person msg, Integer id);
public
void
onthrow(Throwable ex, Integer id);
}
|
class
NofifyImpl
implements
Nofify {
public
Map<Integer, Person> ret =
new
HashMap<Integer, Person>();
public
Map<Integer, Throwable> errors =
new
HashMap<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接口及实现:
<
bean
id
=
"demoCallback"
class
=
"com.alibaba.dubbo.callback.implicit.NofifyImpl"
/>
<
dubbo:reference
id
=
"demoService"
interface
=
"com.alibaba.dubbo.callback.implicit.IDemoService"
version
=
"1.0.0"
group
=
"cn"
>
<
dubbo:method
name
=
"get"
async
=
"true"
onreturn
=
"demoCallback.onreturn"
onthrow
=
"demoCallback.onthrow"
/>
</
dubbo:reference
>
|
(6) TEST CASE:
IDemoService demoService = (IDemoService) context.getBean(
"demoService"
);
NofifyImpl notify = (NofifyImpl) context.getBean(
"demoCallback"
);
int
requestId =
2
;
Person ret = demoService.get(requestId);
Assert.assertEquals(
null
, ret);
//for Test:只是用来说明callback正常被调用,业务具体实现自行决定.
for
(
int
i =
0
; i <
10
; i++) {
if
(!notify.ret.containsKey(requestId)) {
Thread.sleep(
200
);
}
else
{
break
;
}
}
Assert.assertEquals(requestId, notify.ret.get(requestId).getId());
|
更多推荐
所有评论(0)