Dubbo原理及配置

 技术交流群:233513714

Dubbo的背景

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。

Dubbo的应用

用于大规模服务化,通过在消费方获取服务提供方地址列表,实现软负载均衡,减轻硬件压力。

架构

最简单调用图

节点角色说明:

  • Provider: 暴露服务的服务提供方。
  • Consumer: 调用远程服务的服务消费方。
  • Registry: 服务注册与发现的注册中心。
  • Monitor: 统计服务的调用次调和调用时间的监控中心。
  • Container: 服务运行容器。

调用关系说明:

  • 服务容器负责启动,加载,运行服务提供者。
  • 服务提供者在启动时,向注册中心注册自己提供的服务。
  • 服务消费者在启动时,向注册中心订阅自己所需的服务。
  • 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  • 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  • 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

简单用法

需与spring集成(也可以调用api,但官方不推荐,且代码臃肿)。Java代码跟正常功能一样,只需要一个接口和一个实现类,像正常spring一样配置。需要详细说明的是xml配置:

提供者(服务端):

消费者(调用客户端):

代码简单解释(配置项暂不说明):

  • 在服务端只需用<dubbo:service interface="com.ailk.uchannel.dubbo.IDemoService" ref="demoService" timeout="100000"/>这段配置来暴露服务的接口,ref的值是spring配置的bean。
  • 在客户端<dubbo:reference id="demoService" interface="com.ailk.uchannel.dubbo.IDemoService" />,来声明一个spring的bean,然后可以在需要的地方获取这个bean,接着直接调用该接口的所有方法。如:

 

这段代码就是获取spring的配置文件后,获取bean,然后远程调用,并且获取返回值。

使用协议

Dubbo协议

采用NIO复用单一长连接,并使用线程池并发处理请求,减少握手和加大并发效率,性能较好(推荐使用),在大文件传输时,单一连接会成为瓶颈。Dubbo协议缺省每服务每提供者每消费者使用单一长连接,如果数据量较大,可以使用多个连接。示例:

复制代码
<dubbo:protocol name="dubbo" connections="2" />
<!--表示该服务使用JVM共享长连接(缺省)-->
<dubbo:service connections=”0”>或<dubbo:reference connections=”0”>
<!--表示该服务使用独立长连接-->
<dubbo:service connections=”1”>或<dubbo:reference connections=”1”>
<!--表示该服务使用独立两条长连接-->
<dubbo:service connections=”2”>或<dubbo:reference connections=”2”>
复制代码
  • l 连接个数:单连接
  • l 连接方式:长连接
  • l 传输协议:TCP
  • l 传输方式:NIO异步传输
  • l 序列化:Hessian二进制序列化
  • l 适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用dubbo协议传输大文件或超大字符串
  • l 适用场景:常规远程服务方法调用
  • l 协议约束:
  1. 参数及返回值需实现Serializable接口
  2. 参数及返回值不能自定义实现List, Map, Number, Date, Calendar等接口,只能用JDK自带的实现,因为hessian会做特殊处理,自定义实现类中的属性值都会丢失。
  3. 只传成员属性值和值的类型,不传方法或静态变量。
  4. 接口增加方法,对客户端无影响,如果该方法不是客户端需要的,客户端不需要重新部署;输入参数和结果集中增加属性,对客户端无影响,如果客户端并不需要新属性,不用重新部署;输入参数和结果集属性名变化,对客户端序列化无影响,但是如果客户端不重新部署,不管输入还是输出,属性名变化的属性值是获取不到的。

Rmi协议

可与原生RMI互操作,基于TCP协议,偶尔会连接失败,需重建Stub。

如果服务接口继承了java.rmi.Remote接口,可以和原生RMI互操作,即:提供者用Dubbo的RMI协议暴露服务,消费者直接用标准RMI接口调用,或者提供方用标准RMI暴露服务,消费方用Dubbo的RMI协议调用。

如果服务接口没有继承java.rmi.Remote接口,缺省Dubbo将自动生成一个com.xxx.XxxService$Remote的接口,并继承java.rmi.Remote接口,并以此接口暴露服务,但如果设置了<dubbo:protocol name="rmi" codec="spring" />,将不生成$Remote接口,而使用Spring的RmiInvocationHandler接口暴露服务,和Spring兼容。

  • l 连接个数:多连接
  • l 连接方式:短连接
  • l 传输协议:TCP
  • l 传输方式:同步传输
  • l 序列化:Java标准二进制序列化
  • l 适用范围:传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件
  • l 适用场景:常规远程服务方法调用,与原生RMI服务互操作
  • l 协议约束:
  1. 参数及返回值需实现Serializable接口
  2. dubbo配置中的超时时间对rmi无效,需使用java启动参数设置:-Dsun.rmi.transport.tcp.responseTimeout=3000,参见下面的RMI配置。

Hessian协议

可与原生Hessian互操作,基于HTTP协议,需hessian.jar支持,http短连接的开销大。

  • l 连接个数:多连接
  • l 连接方式:短连接
  • l 传输协议:HTTP
  • l 传输方式:同步传输
  • l 序列化:Hessian二进制序列化
  • l 适用范围:传入传出参数数据包较大,提供者比消费者个数多,提供者压力较大,可传文件
  • l 适用场景:页面传输,文件传输,或与原生hessian服务互操作
  • l 协议约束:
  1. 参数及返回值需实现Serializable接口.
  2. 参数及返回值不能自定义实现List, Map, Number, Date, Calendar等接口,只能用JDK自带的实现,因为hessian会做特殊处理,自定义实现类中的属性值都会丢失。

HTTP协议

基于http表单的远程调用协议。

  • l 基于http表单的远程调用协议。
  • l 连接个数:多连接
  • l 连接方式:短连接
  • l 传输协议:HTTP
  • l 传输方式:同步传输
  • l 序列化:表单序列化
  • l 适用范围:传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或URL传入参数,暂不支持传文件。
  • l 适用场景:需同时给应用程序和浏览器JS使用的服务
  • l 协议约束:
  1. 参数及返回值需符合Bean规范

使用协议示例:

<dubbo:protocol name="http" port="8080" />

注意,如果使用servlet派发请求:协议的端口<dubbo:protocol port="8080" />必须与servlet容器的端口相同,协议的上下文路径<dubbo:protocol contextpath="foo" />必须与servlet应用的上下文路径相同。

Webservice协议

基于CXF的frontend-simple和transports-http实现,可以和原生WebService服务互操作,即:提供者用Dubbo的WebService协议暴露服务,消费者直接用标准WebService接口调用,或者提供方用标准WebService暴露服务,消费方用Dubbo的WebService协议调用。

  • l 连接个数:多连接
  • l 连接方式:短连接
  • l 传输协议:HTTP
  • l 传输方式:同步传输
  • l 序列化:SOAP文本序列化
  • l 适用场景:系统集成,跨语言调用
  • l 协议约束:
  1. 参数及返回值需实现Serializable接口
  2. 参数尽量使用基本类型和POJO。

多协议

不同服务不同协议

复制代码
<!-- 多协议配置 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="rmi" port="1099" />
<!-- 使用dubbo协议暴露服务 -->
<dubbo:service interface="com.ailk.uchannel.service.interfaces.HelloService" version="1.0.0" ref="helloService" protocol="dubbo" />
<!-- 使用rmi协议暴露服务 -->
<dubbo:service interface="com.ailk.uchannel.service.interfaces.DemoService" version="1.0.0" ref="demoService" protocol="rmi" />
复制代码

多协议暴露服务

复制代码
<!-- 多协议配置 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="hessian" port="8080" />
<!-- 使用多个协议暴露服务 -->
<dubbo:service id="helloService" interface="com.ailk.uchannel.service.interfaces.HelloService" version="1.0.0" protocol="dubbo,hessian" />
复制代码

Dubbo注册中心

最简单的Multicast注册中心

不需要注册中心,只要广播地址一样,就能相互发现。只是依赖于网络拓普和路由,跨机房有风险。

组播受网络结构限制,只适合小规模应用或开发阶段使用。

示例:

<!-- 使用multicast广播注册中心暴露服务地址 -->
<dubbo:registry address="multicast://224.5.6.7:1234" />

流程说明:

  • l 提供方启动时广播自己的地址
  • l 消费方启动时广播订阅请求
  • l 提供方收到订阅请求时,单播自己的地址给订阅者,如果设置了unicast=false,则广播给订阅者
  • l 消费方收到提供方地址时,连接该地址进行RPC调用。

注意:

  1. multicast地址不能配成127.0.0.1,也不能配成机器的IP地址,必须是D段广播地址,也就是:224.0.0.0到239.255.255.255之间的任意地址。
  2. 为了减少广播量,Dubbo缺省使用单播发送提供者地址信息给消费者,如果一个机器上同时启了多个消费者进程,消费者需声明unicast=false,否则只会有一个消费者能收到消息。

zookeeper 推荐的注册中心

可用集群,官方建议不用复杂配置,也可以选择淘宝的支持。

使用示例:

简单:

<dubbo:registry protocol="zookeeper" address="192.168.109.130:2181"/>

集群的zookeeper:

<dubbo:registry protocol="zookeeper" address="10.20.153.10:2181,10.20.153.11:2181" />

流程说明:

  • l 服务提供者启动时,向/dubbo/com.foo.BarService/providers目录下写入自己的URL地址。
  • l 服务消费者启动时,订阅/dubbo/com.foo.BarService/providers目录下的提供者URL地址,并向/dubbo/com.foo.BarService/consumers目录下写入自己的URL地址。
  • l 监控中心启动时,订阅/dubbo/com.foo.BarService目录下的所有提供者和消费者URL地址。

支持以下功能:

  • l 当提供者出现断电等异常停机时,注册中心能自动删除提供者信息。
  • l 当注册中心重启时,能自动恢复注册数据,以及订阅请求。
  • l 当会话过期时,能自动恢复注册数据,以及订阅请求。
  • l 当设置<dubbo:registry check="false" />时,记录失败注册和订阅请求,后台定时重试。
  • l 可通过<dubbo:registry username="admin" password="1234" />设置zookeeper登录信息。
  • l 可通过<dubbo:registry group="dubbo" />设置zookeeper的根节点,不设置将使用无根树。
  • l 支持*号通配符<dubbo:reference group="*" version="*" />,可订阅服务的所有分组和所有版本的提供者。

zookeeper配置:

在安装路径下的conf目录里,将 zoo_sample.cfg 改名为 zoo.cfg。

  • l 单机:

  需配置项:

    tickTime=2000

    dataDir=D:/devtools/zookeeper-3.2.2/build

    clientPort=2181

  1. tickTime:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime时间就会发送一个心跳。
  2. dataDir:顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。
  3. clientPort:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。
  • l 集群模式:

  集群模式除了上面的三个配置项还要增加下面几个配置项:

  initLimit=5

  syncLimit=2

  server.1=192.168.211.1:2888:3888

  server.2=192.168.211.2:2888:3888

  1. initLimit:这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader 的 Follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 10 个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 5*2000=10 秒
  2. syncLimit:这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime的时间长度,总的时间长度就是 2*2000=4 秒
  3. server.A=B:C:D:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。

  除了以上配置,在data目录下还要放置myid文件:(上面zoo.cfg中的dataDir),这个文件里面就有一个数据就是 A 的值,Zookeeper 启动时会读取这个文件,拿到里面的数据与 zoo.cfg 里面的配置信息比较从而判断到底是那个 server。

简易监控中心

Simple监控中心,用来监控与统计框架运行情况,挂掉不会影响到Consumer和Provider之间的调用,所以用于生产环境不会有风险。

管理控制台 

依托于web容器(可以直接放到tomcat里),开源部分主要包含:路由规则,动态配置,服务降级,访问控制,权重调整,负载均衡,等管理功能,可以查看提供者和消费者的一些信息,并可以对此进行操作。

配置:

需要查看修改/ROOT/WEB-INF/dubbo.properties文件,配置注册中心和登陆用户密码。

dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.admin.root.password=root
dubbo.admin.guest.password=guest

集群使用

特性

  • l 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者。
  • l 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表。
  • l 注册中心对等集群,任意一台宕掉后,将自动切换到另一台。
  • l 服务提供者无状态,任意一台宕掉后,不影响使用。
  • l 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复。
  • l 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者。

容错模式

Failover Cluster

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

Failfast Cluster

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

Failsafe Cluster

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

Failback Cluster

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

Forking Cluster

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

Broadcast Cluster

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

集群模式配置:

<dubbo:service cluster="failsafe" />或者<dubbo:reference cluster="failsafe" />

负载均衡

Random LoadBalance

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

RoundRobin LoadBalance

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

LeastActive LoadBalance

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

ConsistentHash LoadBalance

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

服务容器

服务端不需要web容器,只是一个简单的main方法,加载spring容器,用于暴露服务。

服务容器的加载内容可以扩展,内置了spring, jetty, log4j等加载,可通过Container扩展点进行扩展。

  • l Spring Container

自动加载META-INF/spring目录下的所有Spring配置。

配置:(配在java命令-D参数或者dubbo.properties中)

dubbo.spring.config=classpath*:META-INF/spring/*.xml ----配置spring配置加载位置

  • l Jetty Container

  启动一个内嵌Jetty,用于汇报状态。

  配置:(配在java命令-D参数或者dubbo.properties中)

dubbo.jetty.port=8080 ----配置jetty启动端口

dubbo.jetty.directory=/foo/bar ----配置可通过jetty直接访问的目录,用于存放静态文件

dubbo.jetty.page=log,status,system ----配置显示的页面,缺省加载所有页面
  • l Log4j Container

  自动配置log4j的配置,在多进程启动时,自动给日志文件按进程分目录。

  配置:(配在java命令-D参数或者dubbo.properties中)

dubbo.log4j.file=/foo/bar.log ----配置日志文件路径
dubbo.log4j.level=WARN ----配置日志级别
dubbo.log4j.subdirectory=20880 ----配置日志子目录,用于多进程启动,避免冲突

容器启动:

如:(缺省只加载spring)

java com.alibaba.dubbo.container.Main

或:(通过main函数参数传入要加载的容器)

java com.alibaba.dubbo.container.Main spring jetty log4j

或:(通过JVM启动参数传入要加载的容器)

java com.alibaba.dubbo.container.Main -Ddubbo.container=spring,jetty,log4j

或:(通过classpath下的dubbo.properties配置传入要加载的容器)

dubbo.container=spring,jetty,log4j

基本配置使用说明

Xml配置

  • <dubbo:service/> 服务配置,用于暴露一个服务,定义服务的元信息,一个服务可以用多个协议暴露,一个服务也可以注册到多个注册中心。
  • <dubbo:reference/> 引用配置,用于创建一个远程服务代理,一个引用可以指向多个注册中心。
  • <dubbo:protocol/> 协议配置,用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受。
  • <dubbo:application/> 应用配置,用于配置当前应用信息,不管该应用是提供者还是消费者。
  • <dubbo:module/> 模块配置,用于配置当前模块信息,可选。
  • <dubbo:registry/> 注册中心配置,用于配置连接注册中心相关信息。
  • <dubbo:monitor/> 监控中心配置,用于配置连接监控中心相关信息,可选。
  • <dubbo:provider/> 提供方的缺省值,当ProtocolConfig和ServiceConfig某属性没有配置时,采用此缺省值,可选。
  • <dubbo:consumer/> 消费方缺省配置,当ReferenceConfig某属性没有配置时,采用此缺省值,可选。
  • <dubbo:method/> 方法配置,用于ServiceConfig和ReferenceConfig指定方法级的配置信息。
  • <dubbo:argument/> 用于指定方法参数配置。

Properties文件配置

Dubbo将自动加载classpath根目录下的dubbo.properties,可以通过JVM启动参数:-Ddubbo.properties.file=xxx.properties 改变缺省配置位置。

映射规则:

  • l 将XML配置的标签名,加属性名,用点分隔,多个属性拆成多行:

  比如:dubbo.application.name=foo等价于<dubbo:application name="foo" />

  比如:dubbo.registry.address=10.20.153.10:9090等价于<dubbo:registry address="10.20.153.10:9090" />

  • l 如果XML有多行同名标签配置,可用id号区分,如果没有id号将对所有同名标签生效:

  比如:dubbo.protocol.rmi.port=1234等价于<dubbo:protocol id="rmi" name="rmi" port="1099" /> (协议的id没配时,缺省使用协议名作为id)

  比如:dubbo.registry.china.address=10.20.153.10:9090等价于<dubbo:registry i d="china" address="10.20.153.10:9090" />

详细配置说明参考官方文档。

自带优化功能

结果缓存

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

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

配置如:

复制代码
<dubbo:reference interface="com.ailk.uchannel.service.interfaces.TestService" cache="lru" />

或:

<dubbo:reference interface="com.ailk.uchannel.service.interfaces.TestService">

<dubbo:method name="findBar" cache="lru" />

</dubbo:reference>
复制代码

 

异步调用

基于NIO的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。

示例:

  

代码调用:

  

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

  • l sent="true" 等待消息发出,消息发送失败将抛出异常。
  • l sent="false" 不等待消息发出,将消息放入IO队列,即刻返回。

如果只是异步调用,完全忽略返回值,可以配置return =”false”,j减少Future对象的创建和管理成本。

事件通知

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

示例:

  

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

组合情况:(async=false 默认)

  • l 异步回调模式:async=true onreturn="xxx"
  • l 同步回调模式:async=false onreturn="xxx"
  • l 异步无回调 :async=true
  • l 同步无回调 :async=false

本地存根

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

示例:

  

Stub代码

  

注意:Stub必须有可传入Proxy的构造函数!

本地伪装

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

示例:

  

Mock代码:

  

如果只是想简单的忽略异常,直接return null。

  

 

开发连调与自测

服务分组

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

示例:

服务端

<dubbo:service group="feedback" interface="com.ailk.uchannel.service.interfaces.IndexService" />

<dubbo:service group="member" interface="com.ailk.uchannel.service.interfaces.IndexService" />

客户端

<dubbo:reference id="feedbackIndexService" group="feedback" interface="com.ailk.uchannel.service.interfaces.IndexService" />

<dubbo:reference id="memberIndexService" group="member" interface="com.ailk.uchannel.service.interfaces.IndexService" />

任意组:

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

多版本

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

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

示例:

<dubbo:service interface="com.ailk.uchannel.service.interfaces.TestService" version="1.0.0" />

<dubbo:reference id="testService" interface="com.ailk.uchannel.service.interfaces.TestService" version="1.0.0" />

不区分版本:

<dubbo:reference id="testService" interface="com.ailk.uchannel.service.interfaces.TestService" version="*" />

直连提供者

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

<dubbo:reference id="xxxService" interface="com.ailk.uchannel.service.interfaces.XxxService" url="dubbo://localhost:20890" />

只订阅

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

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

禁用注册配置:

<dubbo:registry address="10.20.153.10:9090" register="false" />

或者

<dubbo:registry address="10.20.153.10:9090?register=false" />

泛化引用

泛接口调用方式主要用于客户端没有API接口及模型类元的情况,参数及返回值中的所有POJO均用Map表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过GenericService调用所有服务实现。

示例配置:

  

Java调用代码:

  

注意:

  1. 基本类型以及Date,List,Map等不需要转换,直接调用;
  2. 用Map表示POJO参数,如果返回值为POJO也将自动转成Map;
  3. 如果是int等类型要用java.lang.Integer等替换,双方接口都要如此。

获取上下文(dubbo的配置)

上下文中存放的是当前调用过程中所需的环境信息。

调用方法:RpcContext.getContext()。

RpcContext是一个ThreadLocal的临时状态记录器,当接收到RPC请求,或发起RPC请求时,RpcContext的状态都会变化。

延迟暴露

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

示例:

延迟5秒暴露服务:

<dubbo:service delay="5000" />

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

<dubbo:service delay="-1" />

Telnet命令

启动服务后可以运行telnet命令,来查看一些信息(window下也可以)。

进入命令:

telnet localhost 20880。

帮助:help。

ls 显示服务列表

  • l ls -l显示服务详细信息列表
  • l ls XxxService显示服务的方法列表
  • l ls -l XxxService显示服务的方法详细信息列表。

Ps 显示服务端口列表

  • l ps -l 显示服务地址列表
  • l ps 20880 显示端口上的连接信息
  • l ps -l 20880 显示端口上的连接详细信息。

Cd 

cd XxxService 改变缺省服务,当设置了缺省服务,凡是需要输入服务名作为参数的命令,都可以省略服务参数。

cd / 取消缺省服务。

Pwd 显示当前缺省服务

Trace 跟踪方法的调用情况

trace XxxService 跟踪1次服务任意方法的调用情况。

trace XxxService 10 跟踪10次服务任意方法的调用情况。

trace XxxService xxxMethod 跟踪1次服务方法的调用情况

trace XxxService xxxMethod 10 跟踪10次服务方法的调用情况。

Count 统计服务的调用情况

count XxxService 统计1次服务任意方法的调用情况。

count XxxService 10 统计10次服务任意方法的调用情况。

count XxxService xxxMethod 统计1次服务方法的调用情况。

Invoke 调用方法

invoke XxxService.xxxMethod({"prop": "value"}) 调用服务的方法。

invoke xxxMethod({"prop": "value"}) 调用服务的方法(自动查找包含此方法的服务)。

Status 显示资源状态

status 显示汇总状态,该状态将汇总所有资源的状态,当全部OK时则显示OK,只要有一个ERROR则显示ERROR,只要有一个WARN则显示WARN。

status -l 显示状态列表。

  

Log 日志

log debug 修改dubbo logger的日志级别。

log 100 查看file logger的最后100字符的日志。

最佳实践

分包

  • l 建议将服务接口,服务模型,服务异常等均放在API包中,因为服务模型及异常也是API的一部分,
  • l 同时,这样做也符合分包原则:重用发布等价原则(REP),共同重用原则(CRP)
  • l 如果需要,也可以考虑在API包中放置一份spring的引用配置,这样使用方,只需在Spring加载过程中引用此配置即可,
  • l 配置建议放在模块的包目录下,以免冲突,如:com/ailk/uchannel/dubbo-reference.xml

粒度

  • l 服务接口尽可能大粒度,每个服务方法应代表一个功能,而不是某功能的一个步骤,否则将面临分布式事务问题,Dubbo暂未提供分布式事务支持。
  • l 服务接口建议以业务场景为单位划分,并对相近业务做抽象,防止接口数量爆炸
  • l 不建议使用过于抽象的通用接口,如:Map query(Map),这样的接口没有明确语义,会给后期维护带来不便。

版本

  • l 每个接口都应定义版本号,为后续不兼容升级提供可能,如:<dubbo:service interface="com.xxx.XxxService" version="1.0" />
  • l 建议使用两位版本号,因为第三位版本号通常表示兼容升级,只有不兼容时才需要变更服务版本。
  • l 当不兼容时,先升级一半提供者为新版本,再将消费者全部升为新版本,然后将剩下的一半提供者升为新版本。

兼容性

  • l 服务接口增加方法,或服务模型增加字段,可向后兼容,删除方法或删除字段,将不兼容,枚举类型新增字段也不兼容,需通过变更版本号升级。
  • l 各协议的兼容性不同,参见: 服务协议

枚举值

  • l 如果是完备集,可以用Enum,比如:ENABLE, DISABLE。
  • l 如果是业务种类,以后明显会有类型增加,不建议用Enum,可以用String代替。
  • l 如果是在返回值中用了Enum,并新增了Enum值,建议先升级服务消费方,这样服务提供方不会返回新值。
  • l 如果是在传入参数中用了Enum,并新增了Enum值,建议先升级服务提供方,这样服务消费方不会传入新值。

序列化

  • l 服务参数及返回值建议使用POJO对象,即通过set,get方法表示属性的对象。
  • l 服务参数及返回值不建议使用接口,因为数据模型抽象的意义不大,并且序列化需要接口实现类的元信息,并不能起到隐藏实现的意图。
  • l 服务参数及返回值都必需是byValue的,而不能是byRef的,消费方和提供方的参数或返回值引用并不是同一个,只是值相同,Dubbo不支持引用远程对象。

异常

  • l 建议使用异常汇报错误,而不是返回错误码,异常信息能携带更多信息,以及语义更友好,
  • l 如果担心性能问题,在必要时,可以通过override掉异常类的fillInStackTrace()方法为空方法,使其不拷贝栈信息,
  • l 查询方法不建议抛出checked异常,否则调用方在查询时将过多的try...catch,并且不能进行有效处理,
  • l 服务提供方不应将DAO或SQL等异常抛给消费方,应在服务实现中对消费方不关心的异常进行包装,否则可能出现消费方无法反序列化相应异常。

调用

  • l 不要只是因为是Dubbo调用,而把调用Try-Catch起来。Try-Catch应该加上合适的回滚边界上。
  • l 对于输入参数的校验逻辑在Provider端要有。如有性能上的考虑,服务实现者可以考虑在API包上加上服务Stub类来完成检验。

推荐用法

在Provider上尽量多配置Consumer端属性

作服务的提供者,比服务使用方更清楚服务性能参数,如调用的超时时间,合理的重试次数,等等

在Provider配置后,Consumer不配置则会使用Provider的配置值,即Provider配置可以作为Consumer的缺省值。

否则,Consumer会使用Consumer端的全局设置,这对于Provider不可控的,并且往往是不合理的。

Provider上尽量多配置Consumer端的属性,让Provider实现者一开始就思考Provider服务特点、服务质量的问题。

在Provider可以配置的Consumer端属性有:

  • l timeout,方法调用超时
  • l retries,失败重试次数,缺省是2(表示加上第一次调用,会调用3次)
  • l loadbalance,负载均衡算法(有多个Provider时,如何挑选Provider调用),缺省是随机(random)。还可以有轮训(roundrobin)、最不活跃优先(leastactive,指从Consumer端并发调用最好的Provider,可以减少的反应慢的Provider的调用,因为反应更容易累积并发的调用)
  • l actives,消费者端,最大并发调用限制,即当Consumer对一个服务的并发调用到上限后,新调用会Wait直到超时。在方法上配置(dubbo:method )则并发限制针对方法,在接口上配置(dubbo:service),则并发限制针对服务。

Provider上配置合理的Provider端属性

Provider上可以配置的Provider端属性有:

  • l threads,服务线程池大小
  • l executes,一个服务提供者并行执行请求上限,即当Provider对一个服务的并发调用到上限后,新调用会Wait(Consumer可能到超时)。在方法上配置(dubbo:method )则并发限制针对方法,在接口上配置(dubbo:service),则并发限制针对服务。

配置dubbo的缓存文件

这个文件会缓存:

  • l 注册中心的列表
  • l 服务提供者列表

使用示例:

<dubbo:registry file=”${user.home}/output/dubbo.cache” />

有了这项配置后,当应用重启过程中,Dubbo注册中心不可用时则应用会从这个缓存文件读取服务提供者列表的信息,进一步保证应用可靠性。

Dubbo优势

  • l 已经成熟,并且被大量项目使用。
  • l 开发简单,同正常开发一样,只需额外配置一段xml(dubbo支持注解方式,会更简单,此处不讨论)。
  • l 可以很容易的切入到当前工程中,无耦合!可以以一个很小的代价在当前的渠道项目中扩展,甚至可以直接使用(无任何修改或者很小修改)原有的接口和方法,并不需要写太多的东西,就可以快速搭起服务平台。

Dubbo劣势

  • l 网上资料不是很多,遇到问题需要上官方查找解决方案。
  • l 由于过于简洁的开发,隐藏太多细节,如有特殊要求,恐怕改动麻烦。
  • l 基本的一些配置,依赖与优化需要投入精力去研究。

注意事项

  • l 单机可以启动多个消费者,但因端口冲突不可以启动多个未经更改的提供者,可在不同机器上运行,或者修改协议的端口。<dubbo:protocol name="dubbo" port="20880" />
  • l 如果使用zookeeper作为注册中心,注意jar或者maven依赖的冲突。
  • l 如果参数在多个地方都有配置(如timeout),循序一下规则:方法级优先,接口级次之,全局配置再次之。如果级别一样,则消费方优先,提供方次之。
  • l 配置覆盖的策略为:JVM启动-D参数优先,XML次之,Properties最后。
  • l 从 dubbo 2.2.0 开始,每个服务默认都会在本地暴露;在引用服务的时候,默认优先引用本地服务;如果希望引用远程服务可以使用一下配置强制引用远程服务,如<dubbo:reference ... scope="remote" />.。

Maven依赖与jar包

  • l Maven依赖
复制代码
<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>${spring.version}</version>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>${spring.version}</version>
  </dependency>

  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.5.3</version>
  </dependency>

  <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.15</version>
    <exclusions>
      <exclusion>
        <groupId>com.sun.jmx</groupId>
        <artifactId>jmxri</artifactId>
      </exclusion>
      <exclusion>
        <groupId>com.sun.jdmk</groupId>
        <artifactId>jmxtools</artifactId>
      </exclusion>
      <exclusion>
        <groupId>javax.jms</groupId>
        <artifactId>jms</artifactId>
      </exclusion>
    </exclusions>
  </dependency>

  <dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.5</version>
  </dependency>

  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.7</version>
    <scope>test</scope>
  </dependency>

  <dependency>
    <groupId>com.github.sgroschupf</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.1</version>
  </dependency>
</dependencies>
复制代码
  • l Jar包

   

总结

个人感觉dubbo适合作为服务平台改造的框架,它可以与当前渠道的工程紧密的集合,而只需要很小的代价,目前测试在资料工程测起服务,在自服务测可以正常访问,并且返回数据库中的结果!如上的工作,只需要加入两个jar包和修正一个jar包冲突(原有javassist替换成javassist-3.15.0-GA )!

如果选择dubbo,需要的是配置的优化与选择,以及后期测试的投入,开发的工作会小到最低,当然不包括开放新的接口。与当前工程的兼容也是需要重点关注的问题!

即使爬到最高的山上,一次也只能脚踏实地地迈一步。
原文地址:https://www.cnblogs.com/cnndevelop/p/9443581.html