官方网站:apache dubbo Github: incubator-dubbo
许多公司采用 dubbo + spring boot + docker 的架构方式。
dubbo 是什么?
dubbo是一个分布式服务框架,提供高性能的以及透明化的RPC远程服务调用解决方案,以及SOA服务治理方案。
dubbo可以解决的问题(解决了不好治理的问题) :
url维护,订单系统会调用用户系统,用户系统也会调用订单系统,调用过多,url无法统一管理 解决方案:注册中心(zookeeper,redis,memcached..) 将对外暴露的服务url全部保存在第三方中间件上
F5负载均衡压力较大,成本较高 解决方案:软负载均衡(zookeeper...)
服务架构依赖混乱,如果理出架构关系? 需要自动整理架构关系的方式
服务器调用量越来越大,服务器的容量问题如何评估?(评估用于扩容指标) 需要监控平台,监控调用量,响应事件
dubbo的核心功能 :
基于url的总线驱动(所有信息都是通过url传递的)
dubbo的架构
核心角色:
复制 Provider
Consumer
Registry
Monitor
Container
Registry 注册中心,Provider将启动后的url注册到Registry
Consumer订阅Registry,Registry在发生变化时,将通知Consumer地址发生变化。
Consumer通过Registry提供的url 远程访问Provider
Monitor负责监控Consumer和Provider,解决调用关系以及调用量的监控问题
dubbo的简单使用
dubbo相关的demo位于: Github
构建provider :
创建order-api模块,用于向其他模块提供接口。其中,此模块中只包含Java业务Server接口和输入输出对象。
创建order-provider模块,此模块依赖order-api模块,是order服务的具体实现;需要依赖dubbo与zkclient
复制 <dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
</dependency>
在 resources/META-INF/spring/
目录下创建 order-consumer.xml
文件,用于定义dubbo服务
复制 <?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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--当前项目在整个分布式架构里面的唯一名称,计算依赖关系的标签-->
<dubbo:application name="order-provider" owner="feathers"/>
<!--dubbo这个服务所要暴露的服务地址所对应的注册中心-->
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183"/>
<!--或者这样写-->
<!--<dubbo:registry address="zookeeper://127.0.0.1:2181?backup=127.0.0.1:2181,127.0.0.1:2181"/>-->
<!--或者不使用注册中心, 这样就不会将提供的接口地址存入到zookeeper中,需要消费者使用url消费-->
<!--<dubbo:registry address="N/A"/>-->
<!--当前服务发布所依赖的协议;webserovice、Thrift、Hessain、http、dubbo-->
<dubbo:protocol name="dubbo" port="20880"/>
<!--服务发布的配置,需要暴露的服务接口-->
<dubbo:service interface="me.feathers.demo.order.OrderService" ref="orderService"/>
<!--暴露的接口的实现-->
<bean id="orderService" class="me.feathers.demo.order.impl.OrderServiceImpl"/>
<!--<dubbo:monitor protocol="registry"/>-->
</beans>
编写启动类,注册服务到注册中心zookeeper
复制 import com.alibaba.dubbo.container.Main;
public static void main(String[] args) throws IOException {
Main.main(args);
System.in.read();
}
查看zookeeper,发现dubbo节点下已经注册了一个url地址,使用此地址,可以直接调用dubbo服务: 
创建consumer:
创建项目client,并在resources
目录下创建配置文件order-consumer.xml
,此配置文件用于配置远程代理对象:
复制 <?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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--当前项目在整个分布式架构里面的唯一名称,计算依赖关系的标签-->
<dubbo:application name="order-provider" owner="feathers"/>
<!--dubbo这个服务所要暴露的服务地址所对应的注册中心-->
<!--<dubbo:registry address="zookeeper://127.0.0.1:2181?backup=127.0.0.1:2182,127.0.0.1:2183"/>-->
<!--生成一个远程服务的调用代理, 使用注册中心获取url调用-->
<!--<dubbo:reference id="orderServices" interface="me.feathers.demo.order.OrderService"/>-->
<!--生成一个远程服务的调用代理, 使用地址直连(测试使用)-->
<dubbo:reference id="orderServices" interface="me.feathers.demo.order.OrderService"
url="dubbo://10.0.75.1:20880/me.feathers.demo.order.OrderService"/>
</beans>
使用Main启动consumer,并且利用创建好的服务代理对象调用服务:
复制 public class App {
public static void main(String[] args) throws IOException {
// 调用服务
// 1. 从spring容器中获取远程服务代理对象
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("order-consumer.xml");
// 2. 调用下单接口
OrderService services = (OrderService) context.getBean("orderServices");
Order order = new Order();
order.setProductName("哈哈");
order.setUserId("u112000");
order.setAmount("$1");
order.setOrderId(123L);
BaseOrderResponse response = services.addOrder(order);
// 3. 展示响应
System.out.println(response);
System.in.read();
}
}
dubbo-admin
dubbo-admin
用来做服务治理,比如控制服务权重、服务路由等。
dubbo-admin
在dubbo2.7
时,被单独列入了一个仓库:Dubbo Admin Github ;如果是2.6版本,则是在dubbo-admin
模块中(地址 )
将项目clone下来,可以依照README 进行配置。
其中,dubbo-admin-ui
是 前端项目,dubbo-admin-server
是接口服务。
修改 dubbo-admin-server\src\main\resources\application.properties
文件的相关内容,配置注册中心地址,用户名密码等信息
在dubbo-admin-server
执行mvn clean package -Dmaven.test.skip=true
将项目打包为jar
进入dubbo-admin-ui
前端项目,执行 npm install
执行完毕后,使用npm run dev
或者 npm run build
运行项目,并打开网址测试
新的dubbo控制台界面如下:
dubbo-monitor
dubbo-monitor
用来监控服务的调用次数,调用关系、响应时间等。
dubbo-monitor-simple 是一个简单的dubbo监控平台,具体安装方式如下:
文件: dubbo-monitor-simple-2.5.3.zip
修改每个dubbo应用程序的xml文件,加入如下配置: <dubbo:monitor protocol="registry"/>
,添加monitor监控
修改配置文件,如下:
复制 dubbo.container=log4j,spring,registry,jetty
dubbo.application.name=simple-monitor
dubbo.application.owner=
# 配置注册中心
# dubbo.registry.address=multicast://224.5.6.7:1234
dubbo.registry.address=zookeeper://127.0.0.1:2181
#dubbo.registry.address=redis://127.0.0.1:6379
#dubbo.registry.address=dubbo://127.0.0.1:9090
dubbo.protocol.port=7070
# jetty容器端口,防止端口占用
dubbo.jetty.port=8099
# jetty存储路径,需要手动创建,否则图表不展示
dubbo.jetty.directory=${user.home}/monitor
# 图表路径,需要手动创建
dubbo.charts.directory=${dubbo.jetty.directory}/charts
# 统计信息路径,需要手动创建
dubbo.statistics.directory=${user.home}/monitor/statistics
# 日志存储路径
dubbo.log4j.file=logs/dubbo-monitor-simple.log
# 日志级别
dubbo.log4j.level=WARN
执行bin/start.sh
或者start.bat
启动服务检查
启动Consumer时,默认回去检测所依赖的服务是否正常提供服务。
复制 <dubbo:reference id="orderServices" interface="me.feathers.demo.order.OrderService"
url="dubbo://10.0.75.1:20880/me.feathers.demo.order.OrderService" check="true"/>
即默认选项为 check=true
;如果将check设置为false,则即使服务不可用,Consumer也可以正常启动。
除此之外,一下标签也拥有check
属性:
dubbo:consumer, check=true/false
: true,没有服务提供者的时候,报错
dubbo:registry, check=true/false
: true,注册订阅失败时报错
telnet命令
复制 telnet localhost 20880
# 进入dubbo控制台,提供了一些命令可以对服务进行操作,类似linux命令
# 查看该dubbo应用程序提供的所有服务
dubbo>ls
me.feathers.demo.order.OrderService
# 进入OrderServer类级别服务
dubbo>cd me.feathers.demo.order.OrderService
Used the me.feathers.demo.order.OrderService as default.
You can cancel default service by command: cd /
# 查看类级别服务下的所有方法级别服务
dubbo>ls
Use default service me.feathers.demo.order.OrderService.
addOrder
deleteOrder
getOrderById
# 查看当前所在的路径
dubbo>pwd
me.feathers.demo.order.OrderService
# 使用invoke调用服务
dubbo>invoke me.feathers.demo.order.OrderService.deleteOrder('oid123')
Use default service me.feathers.demo.order.OrderService.
{"msg":"删除订单成功","code":"00-00"}
elapsed: 2 ms.
# 可以传入对象参数,对象参数是一个json
dubbo>invoke me.feathers.demo.order.OrderService.addOrder({userId:'123', orderId:111, productName:'food', amount:'18.5'})
Use default service me.feathers.demo.order.OrderService.
{"msg":"添加订单成功","code":"00-00"}
elapsed: 0 ms.
# 清屏
clear
Qos
参考 :Dubbo启动时qos-server can not bind localhost:22222错误解决
telnet 默认不可以远程调用,如需配置远程调用,需要加入如下参数:
复制 <dubbo:application name="simple-provider">
<dubbo:parameter key="qos.enable" value="true" />
<dubbo:parameter key="qos.accept.foreign.ip" value="true" />
<dubbo:parameter key="qos.port" value="22223" />
</dubbo:application>
其中,qos是指Quality of Service,是dubbo的在线运维命令,qos.enable
代表是否开启在线运维命令,qos.accept.foreign.ip
是否可以远程连接,qos.port
qos的端口(默认端口22222,如果被占用,则会抛出qos-server can not bind localhost:22222的错误)
多协议的支持
dubbo本身使用的RPC协议是dubbo协议(服务之间的通信协议),同时,dubbo也支持其他协议,比如:
Hessian 协议
Dubbo支持使用Hessian 作为RPC协议,Hessian协议是Caucho 开源的一个RPC框架,Hessian协议底层采用Http作为传输协议,序列化采用Hessian二进制序列化。
Hessian协议底层采用Http通讯,并使用JavaServlet暴露服务,缺省内嵌Jetty作为服务器实现。所以使用Hessian,需要添加两个包,Hessian和Jetty:
复制 <dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.7</version>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
<version>6.1.26</version>
</dependency>
在Provider中添加对Hessian协议的支持:
复制 <!--dubbo支持多协议同时配置,此处增加hessian协议-->
<dubbo:protocol name="hessian" port="8090" server="jetty"/>
此时查看注册中心,就会发现,在providers节点下,多出一个子节点:
客户端需要在调用服务时,指定所使用的RPC协议:
复制 <!--使用protocol指定调用协议-->
<dubbo:reference id="orderServices" interface="me.feathers.demo.order.OrderService" check="true" protocol="hessian"/>
多注册中心支持(不常用)
针对部分服务发布到指定的注册中心,通常用于注册中心分组 、注册中心热备份 等功能,实现很简单,只要在需要进行多注册中心的provider与consumer中添加多个不同的registry即可:
复制 <!--多注册中心支持, 因为是多个注册中心,所以,需要为每个注册中心指定id -->
<dubbo:registry id="reg1" protocol="zookeeper" address="xxx"/>
为指定的服务指定注册中心,只需要在<dubbo:service
上指定注册中心即可:
复制 <dubbo:service interface="me.feathers.demo.order.OrderService" ref="orderService" registry="reg1"/>
服务多版本支持
现在,针对OrderService,系统需要发布一个新的2.0版本,新增一个OrderService实现:OrderServiceImpl2
为了保证旧版用户正常使用,需要配置dubbo服务版本:
复制 <!--服务发布的配置,需要暴露的服务接口-->
<dubbo:service interface="me.feathers.demo.order.OrderService" ref="orderService" version="1.0"/>
<!--新版OrderService-->
<dubbo:service interface="me.feathers.demo.order.OrderService" ref="orderService2" version="2.0"/>
<!--暴露的接口的实现-->
<bean id="orderService" class="me.feathers.demo.order.impl.OrderServiceImpl"/>
<bean id="orderService2" class="me.feathers.demo.order.impl.OrderServiceImpl2"/>
更新provider,在注册中心就会提供两个OrderService服务,一个版本1.0一个版本2.0:

comsumer在使用时,同样需要指定version:
复制 <dubbo:reference id="orderServices" interface="me.feathers.demo.order.OrderService" protocol="hessian" version="2.0"/>
异步调用
注意:异步调用只支持dubbo协议
调用接口时,调用过程可能较慢或者无需同步等待调用的返回结果,此时就需要异步调用。异步调用,服务端无需变动,只需要在客户端方面添加如下配置即可:
复制 <dubbo:reference id="orderServices"
interface="me.feathers.demo.order.OrderService"
async="true"/> <!--异步调用-->
<!--指定某个方法异步调用-->
<dubbo:reference id="orderServices"
interface="me.feathers.demo.order.OrderService"
protocol="dubbo" >
<dubbo:method name="addOrder" async="true"/>
</dubbo:reference>
并使用RpcContext
对象获取调用的返回结果:
复制 //异步调用
services.addOrder(order);
Future<BaseOrderResponse> future = RpcContext.getContext().getFuture();
BaseOrderResponse response = null;
try {
response = future.get(); // 阻塞等待
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// 3. 展示响应
System.out.println(response);
主机绑定
provider发布服务时,ip地址是从何而来的?
复制 ###com.alibaba.dubbo.config.ServiceConfig###
// 1. ProtocolConfig中如果绑定了host,则使用绑定的host
String host = protocolConfig.getHost();
if (provider != null && (host == null || host.length() == 0)) {
// 2. 如果protocolConfig中没有配置,则从providerConfig中获取host
host = provider.getHost();
}
boolean anyhost = false;
if (NetUtils.isInvalidLocalHost(host)) {
anyhost = true;
try {
// 3. 如果上述两个都没有配置host或者都是不正确的host
// 则从InetAddress中获取host
host = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
logger.warn(e.getMessage(), e);
}
// 4. 如果仍然获取不到,则使用
if (NetUtils.isInvalidLocalHost(host)) {
if (registryURLs != null && registryURLs.size() > 0) {
for (URL registryURL : registryURLs) {
try {
Socket socket = new Socket();
try {
// 如果仍然获取不到host,则发起连接到注册中心,再获取连接过去后本地的host
SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
socket.connect(addr, 1000);
host = socket.getLocalAddress().getHostAddress();
break;
} finally {
try {
socket.close();
} catch (Throwable e) {}
}
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
}
if (NetUtils.isInvalidLocalHost(host)) {
// 6. 如果仍然获取不到,使用NetUtils获取
host = NetUtils.getLocalHost();
}
}
}
绑定主机地址:
复制 <!--使用protocolConfig绑定host-->
<dubbo:protocol name="dubbo" port="20880" host="10.35.10.170"/>
<!--使用providerConfig绑定host-->
<dubbo:provider host="10.35.10.170"/>
绑定后的ip:

服务只订阅
服务只订阅是指,在测试环境下,有可能需要调用注册中心的服务,但是又不能将当前服务注册到注册中心的情况,处理也很简单:
复制 <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183" subscribe="true" register="false"/>
<!-- 订阅注册中心的服务,但是不注册当前服务,通常用于测试联调-->
服务只注册
复制 <!--注册中心只注册,常用于多个注册中心的情况-->
<dubbo:registry subscribe="false"/>
连接超时 timeout
一般情况下,一个服务允许的超时时间为 5~10 s。
复制 服务端配置:
<dubbo:provider timeout="5000"/>
客户端配置:
<dubbo:reference id="orderServices"
interface="me.feathers.demo.order.OrderService"
timeout="5000"/>
服务集群和负载均衡
dubbo集群配置比较简单,不需要进行任何配置,只需将服务在其他机器上运行再向注册中心注册一次即可,代码就不再延时。
负载均衡策略
Dubbo共有以下几种负载均衡策略:
Random, 随机分配到某个服务,这是Dubbo默认的负载均衡策略
RoundRobin,轮询:按照公约后的权重设置轮询比率
LeastActive LoadBalance,最少活跃用数,即响应时间较短的服务优先
Consistent LoadBalance,一致性hash
四种负载均衡策略,位于源代码模块dubbo-cluster
的org.apache.dubbo.rpc.cluster.loadbalance
包下。
配置负载均衡策略
复制 <dubbo:service interface="me.feathers.demo.order.OrderService" ref="orderService" loadbalance="xxx"/>
支持的值:
random
roundrobin
leastactive
consistent
集群容错
dubbo提供了以下几种集群容错策略 :
failover cluster:失败时自动切换其他服务器。通过 retries=2
来设置重试次数;这是dubbo的默认策略
failfast cluster:快速失败,只发起一次调用。比如写操作,如新增数据,避免重复插入,这是非幂等请求(发起一次请求与发起多次请求所得的结果时不变的)
failsafe cluster:失败安全,即使失败也不抛异常,比如操作日志
failback cluster:失败自动恢复,后台记录失败请求,定时重发
forking cluster:并行调用多个服务器,只要有一个成功就返回,只能用于读请求(会浪费服务器资源)
broadcast cluster:广播调用所有提供者,其中一台报错就会抛出异常
配置集群容错:
复制 服务端配置:
<dubbo:service interface="me.feathers.demo.order.OrderService" ref="orderService2" version="2.0" cluster="failback"/>
客户端配置:
<dubbo:reference id="orderServices"
interface="me.feathers.demo.order.OrderService"
protocol="dubbo"
version="2.0"
cluster="failback"/>
服务配置优先级
复制 客户端>服务端
服务方法 > 服务
reference.method > service.method > reference > service > consumer > provider
dubbo 企业应用配置
代码展示
分包
复制 web-xxx web模块,提供controller
dubbo-user 用户模块,提供用户相关业务的service
user-api service接口定义,dubbo consumer消费xml配置(放入META-INFO/client下,调用者使用spring import标签引入配置文件,从而注入接口)、dto
user-provider server具体实现
dubbo-order
order-api
order=provider
将order-api与order-provider放入单一项目,其他user也放入单一项目,而不是全部采用多模块的方式放在一起。可以方便项目进行团队管理
服务接口、请求服务模型、异常信息都放在api模块下,复合重用等价原则,共同重用原则
api模块中放入spring的引用配置,也可以放在模块的包目录下。com.xxx.xxx/xx-references.xml
粒度
尽可能将接口设置为粗粒度,每个服务方法代表一个独立的功能,而不是某个功能的步骤。否则会涉及到分布式事务
服务接口建议以业务场景为单位划分。并对相近业务进行抽象,防止接口暴增
不建议使用过于抽象的通用接口,比如泛型,这样的接口没有明确的语义,会使后期维护困难
版本
每个接口都应该定义版本,为后续的兼容性提供前瞻性考虑 (version),注意maven快照版本与发布版本的管理
建议使用两位版本号,第三位版本号则用于表示兼容性升级,只有不兼容时才需要变更服务版本
当接口做到不兼容升级的时候,先升级一半或者一台提供者为新版本,再将消费者全部升级为新版本,最后将所有提供者升级为新版本
推荐配置
再provider端尽可能consumer端中需要配置的属性,比如timeout、retires、loadBalance、线程池大小
配置管理员信息,即在application
中配置的owner
属性,建议配置多个,出问题可以找到相关人员
配置缓存文件
当注册中心集群出现问题,将会造成整个服务瘫痪,可以使用缓存文件,记录注册中心中的状态;当注册中心出现问题,将会从缓存文件中获取服务地址进行使用,进一步保证了服务的可用性。
复制 <dubbo:registry protocol="zookeeper" file="/data/dubbo/cache/zookeeper.cache" address="xxx"/>
缓存文件将会缓存:
配置异常处理
尽量不要使用返回码作为返回信息,推荐使用异常代替。