class="MMTopic1">1 DUBBO概述
DUBBO是阿里巴巴公司的一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
相比于其他服务框架,DUBBO有如下优势:
v 透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入;
v 软负载均衡及容错机制,可在内网替代F5等硬件负载均衡器,降低成本,减少单点;
v 服务自动注册与发现,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。
2 DUBBO功能说明
2.1 服务间相互调用
2.1.1 功能说明
一个完整的业务流程往往涉及到多个服务,通过多个服务的组合完成特定的业务逻辑。
2.1.2 应用场景
服务的组合实现有两种方式,一种为在web的controller层实现,一种是通过服务间调用来实现。
Web层多次调用
服务间调用
以上图为例,存在两个service,顾客service和商品service,如果这两个服务分布在不同的物理机上,则上面两种方式的网络消耗是一致的。但是如果两个服务在同一台物理机上,则服务间调用则比web层多次调用少了一次远程调用。
2.1.3 操作步骤
1) 消费方项目引入服务提供方的api jar包
2)在dubbo服务引用配置文件中添加对应的标签
<dubbo:reference
id="accountService" interface="com.ibm.netbank.store.service.IAccountService">
</dubbo:reference>
3)在CustomerService实现类中添加成员变量,并添加@AutoWired注解让spring自动完成该service的注入。
@Autowired
public IAccountService accountService;
4)代码中调用
accountService.getAccount(name);
2.1.4 注意事项
1、核心服务下沉,形成各个独立的服务中心,尽量避免服务中心间的调用。
2、服务中心的形成需要业务治理辅助
3、需要建立需要依赖专门的服务治理工具,完成以下功能
a) 管理服务间的依赖关系,避免互相依赖及环状调用的情况
b) 计算服务的关键路径,确定服务的启动顺序
建议规范:
1、引入服务间调用后,服务提供方也凑多了服务消费者的角色,建议配置服务提供与服务引用的配置文件分离,分别命名为服务名-provider.xml及服务名-consumer.xml。
2.2 服务异步调用
2.2.1 功能说明
非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务。
2.2.2 应用场景
1、一个业务逻辑中包含多个并行耗时服务调用
2、一个业务逻辑中存在操作时间较长,但后续步骤不依赖于该步骤完成的步骤(如客户下单购买产品后,需要更新产品的点击排名以及客户的使用习惯追踪等)
2.2.3 操作步骤
1)在AccountService中模拟两个耗时操作,分别计算用户美元账户与人民币账户的收益
public double calcuDollarBenifit() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 6000;
}
public double calcuRmbBenifit() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1000;
}
2)spring-multiservices-provider.xml中暴露AccountService服务
<dubbo:service interface="com.ibm.netbank.multiservices.service.IAccountService" ref="accountService"/>
3)消费者方在consumer.xml中引用Account服务,针对两个耗时调用声明为异步调用,并根据自身需求设置timeout时间。
<dubbo:reference
id="accountService" interface="com.ibm.netbank.multiservices.service.IAccountService" url="dubbo://127.0.0.1:20888">
<dubbo:method name="calcuDollarBenifit" async="true" timeout="10000"/>
<dubbo:method name="calcuRmbBenifit" async="true" timeout="10000"/>
</dubbo:reference>
4)消费者通过调用实现业务逻辑,本例中美元账户收益计算耗时5s,人民币账户收益计算耗时2s,异步并发调用后,5s即可返回结果。
public double getBenifitAsyn() {
accountService.calcuDollarBenifit();
Future<Double> dollarFuture = RpcContext.getContext().getFuture();
accountService.calcuRmbBenifit();
Future<Double> rmbFuture = RpcContext.getContext().getFuture();
double total = 0.0;
try {
double dollar = dollarFuture.get();
double rmb = rmbFuture.get();
total = dollar+rmb;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return total;
}
2.2.4 注意事项
1、需要根据实际业务情况设置超时,否则会抛出timeoutException
2、异步的调用具有传递性,如下图,A服务声明通过异步方式调用B服务,B服务声明通过同步方式调用C服务,但实际调用过程中B调用C服务时也会立刻返回Null。
3、如在统计等场景下,完全忽略返回值,可以配置return="false",以减少Future对象的创建和管理成本
<dubbo:method name="findFoo" async="true" return="false" />
2.3 集群容错配置
2.3.1 功能说明
当集群调用失败时,可以选择不同的容错方案,缺省值为failover
2.3.2 应用场景
参数应用说明:
1.failover:失败自动切换,当出现失败,重试其它服务器。
通常用于读操作,但重试会带来更长延迟。
2.failfast:快速失败,只发起一次调用,失败立即报错。
通常用于非幂等性的写操作,比如新增记录。
3.failsafe:失败安全,出现异常时,直接忽略。
通常用于写入审计日志等操作。
4.failback:失败自动恢复,后台记录失败请求,定时重发。
通常用于消息通知操作。
4.forking:并行调用多个服务器,只要一个成功即返回。
通常用于实时性要求较高的读操作,但需要浪费更多服务资源。
可通过forks="2"来设置最大并行数。
4.broadcast:广播调用所有提供者,逐个调用,任意一台报错则报错。
通常用于通知所有提供者更新缓存或日志等本地资源信息。
如果没有特殊需求,推荐使用failover
2.3.3 操作步骤
容错方式可以在Consumer或者Provider端进行配置,Consumer端配置优先于Provider端,
推荐在Provider端进行配置,因为服务提供者比服务消费者更清楚服务的性能参数
Provider端配置示例:
<dubbo:service interface="xxx" cluster="failover" />
Consumer端配置示例:
<dubbo:reference interface="xxx" cluster="failover" />
2.3.4 注意事项
1.其他与集群容错相关的参数说明:
1)timeout: 方法调用超时时间,缺省为1000
2)retries: 失败重试次数,缺省为2(表示加上第一次调用,会调用3次)
3)actives: Consumer端最大并发调用限制,当Consumer对一个服务的并发调用到上限后,新调用会Wait直到超时
2.failfast及failsafe都只发起一次调用,failfast失败会在Consumer端抛出异常,failsafe失败会被直接忽略,并在Consumer端返回null值
2.4 负载均衡配置
2.4.1 功能说明
在调用服务时,Dubbo提供了多种软负载均衡策略,缺省为random随机调用
2.4.2 应用场景
参数应用说明:
1.Random LoadBalance:
随机,按权重设置随机概率。
在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
2.RoundRobin LoadBalance:
轮循,按公约后的权重设置轮循比率。
存在慢的提供者累积请求问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
3.LeastActive LoadBalance:
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
4.ConsistentHash LoadBalance:
一致性Hash,相同参数的请求总是发到同一提供者。
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
如果没有特殊需求,推荐使用random随机调用
2.4.3 操作步骤
负载均衡策略,可以在Consumer或者Provider端进行配置,并可以选择针对接口或者方法进行配置,配置的覆盖规则:
1) 方法级配置别优于接口级别 2) Consumer端配置优于Provider配置
推荐在Provider端进行配置,因为服务提供者比服务消费者更清楚服务的性能参数
针对服务配置示例:
Provider端: <dubbo:service interface="xxx" loadbalance="random" />
Consumer端: <dubbo:reference interface="xxx" loadbalance="random" />
针对方法配置示例:
Provider端: <dubbo:service interface="xxx" >
<dubbo:method name=”xxx” loadbalance=”random”/>
</dubbo:service>
Consumer端: <dubbo:reference interface="xxx" >
<dubbo:method name=”xxx” loadbalance=”random”/>
</dubbo:reference>
2.4.4 注意事项
如果选择random或者roundrobin,可以通过weight值设置服务权重,默认为100,此参数通常用于容量评估,配置示例:
<dubbo:service interface="xxx" weight="1000" />
2.5 服务分组
2.5.1 功能说明
主要是通过在服务提供端和服务消费端的服务声明配置文件中分别声明group的属性,从而达到对于同一个接口具有不同实现的情况下,对不同实现进行区分调用的目的。
2.5.2 应用场景
服务分组的功能主要是应用于对同一个接口存在多种不同的实现的场景下。
2.5.3 操作步骤
1. 在服务提供端服务声明的配置文件中(配置文件所在位置按照当前的目录结构应为src/main/resources/META-INF/spring/spring-xx.xml文件)增加group配置选项,如下所示:
<dubbo:service interface="com.ibm.netbank.demo.service.IPersonService" ref="personService" version="2.0" group="zh" />
<dubbo:service interface="com.ibm.netbank.demo.service.IPersonService" ref="personServiceNew" version="2.0" group="en" />
2. 在服务消费端接口声明的配置文件中(配置文件所在位置按照当前的目录结构应为src/main/resources/dubboContext.xml)增加group配置选项,并通过group配置选项指定要调用的接口实现组,如下所示:
<dubbo:reference id="personService" interface="com.ibm.netbank.demo.service.IPersonService" version="2.0" group="en" />
2.5.4 注意事项
服务进行分组后,可以通过DUBBO自带的telnet工具(注意DUBBO提供的端口为20880)查看服务的运行情况,如上例中配置了两个group的服务实现,则通过DUBBO的ls -l命令可以看到实现了同一个接口的这两个服务的情况,如下所示:
com.ibm.netbank.demo.service.IPersonService -> dubbo://10.232.98.185:20880/c
om.ibm.netbank.demo.service.IPersonService?anyhost=true&application=netbank-
sample-service&dubbo=2.5.3&group=zh&interface=com.ibm.netbank.demo.service.I
PersonService&methods=deletePerson,checkName,getAllPeople,seachPeopleByName,getP
ersonById,addPerson,updatePerson&pid=2644&revision=2.0&side=provider×tamp=1
381819869792&version=2.0
com.ibm.netbank.demo.service.IPersonService -> dubbo://10.232.98.185:20880/c
om.ibm.netbank.demo.service.IPersonService2?anyhost=true&application=netbank
-sample-service&dubbo=2.5.3&group=en&interface=com.ibm.netbank.demo.service.
IPersonService&methods=deletePerson,checkName,getAllPeople,seachPeopleByName,get
PersonById,addPerson,updatePerson&pid=2644&revision=2.0&side=provider×tamp=
1381819870822&version=2.0
2.6 服务多版本
2.6.1 功能说明
主要是通过在服务提供端和服务消费端的服务声明配置文件中分别声明version的属性,从而达到可以调用不同版本服务的目的。
2.6.2 应用场景
服务多版本的功能可以应用于如下场景:
1. 在本地测试中,为避免使用组播造成在修改同一个服务时的交叉调用,可以使用版本号进行区分;
2. 在针对同一个接口出现不兼容的升级时,可以采用版本号的方式实现服务的按批次升级。
2.6.3 操作步骤
3. 在服务提供端服务声明的配置文件中(配置文件所在位置按照当前的目录结构应为src/main/resources/META-INF/spring/spring-xx.xml文件)增加version配置选项,如下所示:
<dubbo:service interface="com.ibm.netbank.demo.service.IPersonService" ref="personService" version="2.0" />
4. 在服务消费端接口声明的配置文件中(配置文件所在位置按照当前的目录结构应为src/main/resources/dubboContext.xml)增加version配置选项,如下所示:
<dubbo:reference id="personService" interface="com.ibm.netbank.demo.service.IPersonService" version="2.0" />
2.6.4 注意事项
1. 若提供端和消费端版本不一致,则会如下版本不一致错误:
Failed to check the status of the service com.ibm.netbank.demo.service.IPersonService. No provider available for the service com.ibm.netbank.demo.service.IPersonService:2.0.1 from the url multicast://224.5.6.11:1234/com.alibaba.dubbo.registry.RegistryService?application=netbank-sample-web&dubbo=2.5.3&interface=com.ibm.netbank.demo.service.IPersonService&methods=deletePerson,checkName,getAllPeople,seachPeopleByName,getPersonById,addPerson,updatePerson&pid=6840&revision=0.0.1-SNAPSHOT&side=consumer×tamp=1381540308102&version=2.0.1 to the consumer 10.232.98.185 use dubbo version 2.5.3
当出现上述问题时,可以通过DUBBO自带的telnet工具(注意DUBBO提供的端口为20880),并通过ls -l命令来查看当前服务的详细信息,其中包括服务的版本信息,如下所示:
com.ibm.netbank.demo.service.IPersonService -> dubbo://10.232.98.185:20880/c
om.ibm.netbank.demo.service.IPersonService?anyhost=true&application=netbank
sample-service&dubbo=2.5.3&interface=com.ibm.netbank.demo.service.IPersonSer
vice&methods=deletePerson,checkName,getAllPeople,seachPeopleByName,getPersonById
,addPerson,updatePerson&pid=7672&revision=2.0.0&side=provider×tamp=13815399
77658&version=2.0.0
2. 针对服务的版本号,建议使用两位编码样式,如1.0、2.1,在生产环境中,通常在出现版本不兼容的情况下才使用序列号,另外需要注意的是,在不使用version属性时,系统会将服务版本缺省赋予0.0.0的版本号。
2.7 结果缓存
2.7.1 功能说明
结果缓存是指方法的结果被存储到缓存,当下次相同参数的方法调用时返回缓存的结果。
当目标方法执行时,架构会检查指定参数的方便是否已经被执行过,如果没有则执行,并缓存结果返回;否则直接返回缓存结果并不执行方法。
Dubbo提供lru, threadlocal, jcache三种类型的缓存。
根据http://code.alibabatech.com/jira/browse/DUBBO-241 ,http://www.99jianzhu.com 曾经还存在其他可扩展缓存类型:lru, threadlocal, jcache, ehcache, oscache等。并能够通过RpcContext.getContext().clear();清除本次调用的缓存。
但在代码中仅见lru, threadlocal, jcache三种缓存的代码。jcache 与JSR107集成,可以桥接各种缓存实现,缓存类型可扩展。参见:CacheFactory扩展点。
2.7.2 应用场景
适用于参数相同时,方法执行结果不会变的方法。
LRU缓存如字面Least Recent Used所示,设计为在缓存满时删除最长时间未使用的缓存,保持最热的数据被缓存。
Threadlocal缓存是当前线程缓存,通过ThreadLocal变量为各线程隔离分别存储,用于同一线程内的变量缓存。Threadlocal缓存使用Map存储,未设定最大缓存量值。
2.7.3 配置方法
与Spring相比,Dubbo未实现@Cacheable和@Caching等基于注解的声明,而是使用XML声明。
在声明服务的配置文件中为所需缓存的服务或方法增加cache配置选项,如下所示:
标签
属性
类型
是否必填
作用
描述
兼容性
<dubbo:reference>
cache
string/boolean
可选
服务治理
以调用参数为key,缓存返回结果,可选:lru, threadlocal, jcache等
Dubbo2.1.0及其以上版本支持
<dubbo:consumer>
cache
string/boolean
可选
服务治理
以调用参数为key,缓存返回结果,可选:lru, threadlocal, jcache等
Dubbo2.1.0及其以上版本支持
在Dubbo服务注册后,其中提供方和消费方均可对缓存进行配置。
如消费方可:
<dubbo:reference interface="com.foo.BarService" cache="lru" />
也可:
<dubbo:reference interface="com.foo.BarService">
<dubbo:method name="findBar" cache="lru" />
</dubbo:reference>
服务方也可:
<dubbo:service interface="com.foo.BarService" ref="BarService" cache="lru"/>
根据http://code.alibabatech.com/jira/browse/DUBBO-241
曾经可以使用 cache-size="100" 来配置LRU缓存大小,但看起来为了和其他接口统一,已经取消,大小配置已不可用。如确需修改缓存大小,可考虑修改common.Utils.LRUCache代码。
使用方法可参照dubbo-master\dubbo-test\dubbo-test-examples的cache样例。
2.7.4 注意事项
1、support.lru.LruCache默认缓存大小为1000,但仅提供是否过大的removeEldestEntry()函数,而未自我动态维护,也未存储顺序(用Map缓存)。实际使用的是common.Utils.LRUCache,它使用LinkedHashMap维护队列。
2、cache="true"和cache="lru"有着相同的效果。
3、通过实验发现,在客户端和服务端只要有一端配置即可,当这两个配置不同时,以cache-consumer端配置的为准。
4、Dubbo缓存文件
缓存文件配置如:<dubbo:registry file=”${user.hoome}/output/dubbo.cache”>
默认会在用户文件夹\.dubbo\下根据广播地址分文件注册,如C:\Users\DELL\.dubbo\dubbo-registry-224.5.6.7.cache。
这个文件会缓存:注册中心的列表、服务提供者列表。可供察看验证。
应当注意:文件的路径,应用可以根据需要调整,保证这个文件不会在发布过程中被清除。如果有多个应用进程注意不要使用同一个文件,避免内容被覆盖。
有了这项配置后,当应用重启过程中,Dubbo注册中心不可用时,应用会从这个缓存文件读取服务提供者列表的信息,进一步保证应用可靠性。
2.8 隐式传参
2.8.1 功能说明
隐式传参用于在服务方和消费方之间进行参数传递。在通过RPC调用服务时,通过Invoker将RpcContext的Attachment变量传输到被调用方,以供服务端使用。
2.8.2 应用场景
隐式传参用于在服务方和消费方之间进行参数传递。特别是不宜明文传输参数的时候,或传递数量较大的时候。
2.8.3 操作步骤
1 一方在调用前使用setAttachment()设置参数,
2 调用以保证双方在同一RPCContext下
3 另一方即可使用getAttachment()获取参数。
{
RpcContext.getContext().setAttachment("index", "1"); // 隐式传参,后面的远程调用都会隐式将这些参数发送到服务器端,类似cookie,用于框架集成,不建议常规业务使用
xxxService.xxx(); // 远程调用
}
……
{
String index = RpcContext.getContext().getAttachment("index"); // 获取客户端隐式传入的参数
}
2.8.4 注意事项
1、RpcContext状态
RpcContext是一个ThreadLocal的临时状态记录器,当接收到RPC请求,或发起RPC请求时,RpcContext的状态都会变化。比如:A调B,B再调C,则B机器上,在B调C之前,RpcContext记录的是A调B的信息,在B调C之后,RpcContext记录的是B调C的信息。
2、禁用的特殊Key
path,group,version,dubbo,token,timeout几个key有特殊处理,如在invoke时会清空、重设等。如果客户端错配了这些参数会影响服务的执行或无法取到希望的结果。
清空位置:com.alibaba.dubbo.rpc.filter.ContextFilter
如确需访问这些,在直接获取不到时可考虑如下不规范的方法获取。
示例:
String token = RpcContext.getContext().getInvocation().getAttachements().get("token");
3、节约网络负载
RPCContext的attachments存储在HashMap<String, String> 中,setAttachment设置的KV,在完成下面一次远程调用前会被清空。即多次远程调用要多次设置。网络传输负载较大,且客户端可以任意设置参数,服务端无法控制数量。客户端应自觉减少设置服务端不需要的参数。
清空位置: dubbo/dubbo-rpc/dubbo-rpc-api/src/main/java/com/alibaba/dubbo/rpc/filter/ConsumerContextFilter.java
AbstractInvoker拷贝attachments曾经很慢。
根据http://code.alibabatech.com/jira/browse/DUBBO-444 ,曾有一个版本,当并发很高时,有很多线程都在attachments拷贝。
现已改进。
4、在服务中仅设置局部变量
Attachment不能用于service的类变量、实例变量设置,仅能用于局部变量。否则在多个consumer利用同一service时,多线程设置同一实例的变量时会互相影响。
如:
Consumer不停将index设置为1-100,Consumer2不停将index设置为100-200。
Consumer:
AttachmentService attachmentService = (AttachmentService) context.getBean("AttachmentService");
while(true) {
for(int i = 0; i < 100; i++) {
RpcContext.getContext().setAttachment("index", (String.valueOf(i)));
System.out.println("index is " + attachmentService.getAtt());
}
}
Service:
public class AttachmentServiceImpl implements AttachmentService{
String index = "origin"; //应改为局部变量
public String getAtt() {
if(RpcContext.getContext().isProviderSide()) {
if(RpcContext.getContext().getAttachment("index") != null) {
index = (String)RpcContext.getContext().getAttachment("index");
System.out.println(" New index " + " from " + RpcContext. getContext(). getRemoteAddressString() + "is set to " + index);
}
return index;
}
return "not ProviderSide";
}
}
同时运行Consumer和Consumer2,由于index为实例变量,它们向service设置的Attachment会互相混淆。表现为consumer显示1-100与100-200的数字交替出现。
2.9 事件通知
2.9.1 功能说明
在调用之前,调用之后,出现异常时,会触发oninvoke, onreturn, onthrow三个事件,可以配置当事件发生时,通知哪个类的哪个方法。
2.9.2 应用场景
在消费者端实现,对服务方透明,且可与主业务逻辑分开,可用来实现一些非核心业务或者统计需求。
2.9.3 操作步骤
1、实现事件通知类
class DemoNofifyImpl {
public void onreturn(List<Person> msg) {
System.out.println("onreturn:" + msg);
}
public void onreturn2(Person person,Long id) {
System.out.println("onreturn2:" + person.getName());
}
public void onthrow(Throwable ex) {
System.out.println("onethrow"+ex.toString());
}
public void oninvoke(List<Person> msg) {
System.out.println("oninvoke");
}
}
2、服务方consumer.xml中增加事件通知类的bean标签与需要事件通知的方法
<dubbo:reference
id="personService" interface="com.ibm.netbank.demo.service.IPersonService" >
<dubbo:method name="getAllPeople" async="false" onreturn ="demoCallback.onreturn" onthrow="demoCallback.onthrow"/>
<dubbo:method name="getPersonById" async="true" onreturn ="demoCallback.onreturn2" timeout="5000"/>
</dubbo:reference>
2.9.4 注意事项
1、onreturn onthrow 可用,oninvoke使用时spring报错,无法完成注入
2、在定义回调处理方法时,需要按照以下规则匹配原service方法
onreturn(service返回值,service原参数1,service原参数2...)
onthrow(Throwable ex,service原参数1,service原参数2...)
2.10 日志设置
2.10.1 功能说明
如果需要记录每一次请求信息,可开启访问日志,类似于apache的访问日志。
2.10.2 应用场景
可用于测试,或线上开启一台机器用于排查问题。
2.10.3 操作步骤
将访问日志输出到当前应用的log4j日志:
<dubbo:protocol accesslog="true"/>
将访问日志输出到指定文件:
<dubbo:protocol accesslog="foo/bar.log"/>
2.10.4 注意事项
此日志量比较大,请注意磁盘容量
为了对日志进行统一处理,建议采用全局日志框架,对各层的日志以及日志级别和开关进行统一考虑。