Dubbo分布式治理

官方网站:apache dubbo Github: incubator-dubbo

许多公司采用 dubbo + spring boot + docker 的架构方式。

dubbo 是什么?

dubbo是一个分布式服务框架,提供高性能的以及透明化的RPC远程服务调用解决方案,以及SOA服务治理方案。

dubbo可以解决的问题(解决了不好治理的问题)

  1. url维护,订单系统会调用用户系统,用户系统也会调用订单系统,调用过多,url无法统一管理 解决方案:注册中心(zookeeper,redis,memcached..) 将对外暴露的服务url全部保存在第三方中间件上

  2. F5负载均衡压力较大,成本较高 解决方案:软负载均衡(zookeeper...)

  3. 服务架构依赖混乱,如果理出架构关系? 需要自动整理架构关系的方式

  4. 服务器调用量越来越大,服务器的容量问题如何评估?(评估用于扩容指标) 需要监控平台,监控调用量,响应事件

dubbo的核心功能

  1. 远程通信

  2. 集群容错

  3. 服务自动发现

  4. 负载均衡

  5. 基于url的总线驱动(所有信息都是通过url传递的)

dubbo的架构

核心角色:

Provider
Consumer
Registry
Monitor
Container
  • Provider 提供服务,比如tomcat

  • Registry 注册中心,Provider将启动后的url注册到Registry

  • Consumer订阅Registry,Registry在发生变化时,将通知Consumer地址发生变化。

  • Consumer通过Registry提供的url 远程访问Provider

  • Monitor负责监控Consumer和Provider,解决调用关系以及调用量的监控问题

dubbo的简单使用

dubbo相关的demo位于: Github

构建provider

  1. 创建order-api模块,用于向其他模块提供接口。其中,此模块中只包含Java业务Server接口和输入输出对象。

  2. 创建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>
  3. 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>
  4. 编写启动类,注册服务到注册中心zookeeper

     import com.alibaba.dubbo.container.Main;
    
     public static void main(String[] args) throws IOException {
        Main.main(args);
        System.in.read();
    }
  5. 查看zookeeper,发现dubbo节点下已经注册了一个url地址,使用此地址,可以直接调用dubbo服务: ![](../../../../../../OneDrive/note/Java/Distributed/images/20190313205831318_24519.png =687x)

创建consumer:

  1. 创建项目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>
  2. 使用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-admindubbo2.7时,被单独列入了一个仓库:Dubbo Admin Github;如果是2.6版本,则是在dubbo-admin模块中(地址)

将项目clone下来,可以依照README进行配置。

其中,dubbo-admin-ui 是 前端项目,dubbo-admin-server 是接口服务。

  1. 修改 dubbo-admin-server\src\main\resources\application.properties文件的相关内容,配置注册中心地址,用户名密码等信息

  2. dubbo-admin-server 执行mvn clean package -Dmaven.test.skip=true 将项目打包为jar

  3. 执行jar文件,启动接口服务

  4. 进入dubbo-admin-ui 前端项目,执行 npm install

  5. 执行完毕后,使用npm run dev 或者 npm run build 运行项目,并打开网址测试

新的dubbo控制台界面如下:

dubbo-monitor

dubbo-monitor用来监控服务的调用次数,调用关系、响应时间等。

dubbo-monitor-simple 是一个简单的dubbo监控平台,具体安装方式如下:

文件: dubbo-monitor-simple-2.5.3.zip

  1. 修改每个dubbo应用程序的xml文件,加入如下配置: <dubbo:monitor protocol="registry"/>,添加monitor监控

  2. 修改配置文件,如下:

    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
  3. 执行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.portqos的端口(默认端口22222,如果被占用,则会抛出qos-server can not bind localhost:22222的错误)

多协议的支持

dubbo本身使用的RPC协议是dubbo协议(服务之间的通信协议),同时,dubbo也支持其他协议,比如:

  • dubbo

  • RMI

  • Hessian

  • WebService

  • Http

  • Thrift

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:

![](../../../../../../OneDrive/note/Java/Distributed/images/20190318101350399_21345.png =730x)

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:

![](../../../../../../OneDrive/note/Java/Distributed/images/20190318110512123_17445.png =834x)

服务只订阅

服务只订阅是指,在测试环境下,有可能需要调用注册中心的服务,但是又不能将当前服务注册到注册中心的情况,处理也很简单:

<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-clusterorg.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
  1. 将order-api与order-provider放入单一项目,其他user也放入单一项目,而不是全部采用多模块的方式放在一起。可以方便项目进行团队管理

  2. 服务接口、请求服务模型、异常信息都放在api模块下,复合重用等价原则,共同重用原则

  3. api模块中放入spring的引用配置,也可以放在模块的包目录下。com.xxx.xxx/xx-references.xml

粒度

  1. 尽可能将接口设置为粗粒度,每个服务方法代表一个独立的功能,而不是某个功能的步骤。否则会涉及到分布式事务

  2. 服务接口建议以业务场景为单位划分。并对相近业务进行抽象,防止接口暴增

  3. 不建议使用过于抽象的通用接口,比如泛型,这样的接口没有明确的语义,会使后期维护困难

版本

  1. 每个接口都应该定义版本,为后续的兼容性提供前瞻性考虑 (version),注意maven快照版本与发布版本的管理

  2. 建议使用两位版本号,第三位版本号则用于表示兼容性升级,只有不兼容时才需要变更服务版本

  3. 当接口做到不兼容升级的时候,先升级一半或者一台提供者为新版本,再将消费者全部升级为新版本,最后将所有提供者升级为新版本

推荐配置

  • 再provider端尽可能consumer端中需要配置的属性,比如timeout、retires、loadBalance、线程池大小

  • 配置管理员信息,即在application中配置的owner属性,建议配置多个,出问题可以找到相关人员

配置缓存文件

当注册中心集群出现问题,将会造成整个服务瘫痪,可以使用缓存文件,记录注册中心中的状态;当注册中心出现问题,将会从缓存文件中获取服务地址进行使用,进一步保证了服务的可用性。

<dubbo:registry protocol="zookeeper" file="/data/dubbo/cache/zookeeper.cache" address="xxx"/>

缓存文件将会缓存:

  • 注册中心列表

  • 服务提供者列表

配置异常处理

尽量不要使用返回码作为返回信息,推荐使用异常代替。

最后更新于