发生服务循环消费时候关闭服务启动检查

默认情况下,若服务消费者先于服务提供者启动,则消费者端会报错。因为默认情况下消费者会在启动时查检其要消费的服务的提供者是否已经注册,若未注册则抛出异常。**在消费者端的 spring 配置文件中添加 ****check="false"**属性,则可关闭服务检查功能。

在循环消费场景下是必须要使用的。A 消费 B 服务,B 消费 C 服务,而 C 消费 A 服务。必须至少有一方要关闭服务检查功能,否则将无法启动任何一方。

多版本控制实现灰度发布

系统升级采用的**“灰度发布(又称为金丝雀发布)” 是在低压力时段,让部分消费者先调用新的提供者实现类,其余的仍然调用老的实现类,在新的实现类运行没有问题的情况下,逐步让所有消费者全部调用成新的实现类。**

1
2
3
4
5
6
7
<!--指定消费0.0.1版本,即oldService提供者-->
<!--<dubbo:reference id="someService" version="0.0.1"-->
<!--interface="com.abc.service.SomeService"/>-->

<!--指定消费0.0.2版本,即newService提供者-->
<dubbo:reference id="someService" version="0.0.2"
interface="com.abc.service.SomeService"/>
1
2
3
4
5
6
7
8
9
10
<!--注册Service实现类-->
<bean id="oldService" class="com.abc.provider.OldServiceImpl"/>
<bean id="newService" class="com.abc.provider.NewServiceImpl"/>

<!--暴露服务-->
<dubbo:service interface="com.abc.service.SomeService"
ref="oldService" version="0.0.1"/>
<dubbo:service interface="com.abc.service.SomeService"
ref="newService" version="0.0.2"/>

服务分组实现统一接口支撑多业务

**服务分组与多版本控制的使用方式几乎是相同的,只要将 version 替换为 group 即可。 **

**使用版本控制的目的是为了升级,是为了替换。分组是统一接口为了不同的业务提供的不同实现。**这些实现所提供的服务是并存的,例如,对于支付服务的实现,可以有微信支付实现与支付宝支付实现等。

1
2
3
4
5
6
<!--指定调用微信服务-->
<dubbo:reference id="weixin" group="pay.weixin"
interface="com.abc.service.SomeService"/>
<!--指定调用支付宝服务-->
<dubbo:reference id="zhifubao" group="pay.zhifubao"
interface="com.abc.service.SomeService"/>
1
2
3
4
5
6
7
8
9
<!--注册Service实现类-->
<bean id="weixinService" class="com.abc.provider.WeixinServiceImpl"/>
<bean id="zhifubaoService" class="com.abc.provider.ZhifubaoServiceImpl"/>

<!--暴露服务-->
<dubbo:service interface="com.abc.service.SomeService"
ref="weixinService" group="pay.weixin"/>
<dubbo:service interface="com.abc.service.SomeService"
ref="zhifubaoService" group="pay.zhifubao"/>

多协议方式提供统一服务

大数据小并发用短连接协议,小数据大并发用长连接协议。

协议 连接个数 连接方式 传输协议 传输方式 适用范围
dubbo 单连接 长连接 TCP NIO 异步传输 数据包大小建议小于100k,消费者比提供者个数多,尽量不传输大文件或超大字符串。
rmi 多连接 短连接 TCP BIO同步传输 传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件。
hession 多连接 短连接 HTTP BIO同步传输 传入传出参数数据包较大,提供者比消费者个数多,提供者可传文件
http 多连接 短连接 HTTP BIO同步传输 传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看, 可用表单或 URL 传入参数,暂不支持传文件。
webservice 多连接 短连接 HTTP BIO同步传输 系统集成,跨语言调用
thrift Thrift 是 Facebook 捐给 Apache 的一个 RPC 框架,其消息传递采用的协议即为 thrift 协议。当前 dubbo 支持的 thrift 协议是对 thrift 原生协议的扩展。Thrift 协议不支持 null 值的传递。
memcached
/redis
它们都是高效的 KV 缓存服务器。它们会对传输的数据使用相应的技术进行缓存。
rest 若需要开发具有 RESTful 风格的服务,则需要使用该协议。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 声明要使用的多种协议 -->
<dubbo:protocol name="dubbo" port="20880"/>
<dubbo:protocol name="rmi" port="1099"/>

<!--注册Service实现类-->
<bean id="oldService" class="com.abc.provider.OldServiceImpl"/>
<bean id="newService" class="com.abc.provider.NewServiceImpl"/>

<!--暴露服务-->
<dubbo:service interface="com.abc.service.SomeService"
ref="oldService" version="0.0.1"
protocol="rmi, dubbo"/>
<dubbo:service interface="com.abc.service.SomeService"
ref="newService" version="0.0.2"/>

在提供者中要首先声明新添加的协议,然后在服务dubbo:service/标签中再增加该新的协议。若不指定,默认为 dubbo 协议

1
2
3
4
5
6
7
8
9
<!--指定消费0.0.1版本,即newService提供者-->
<dubbo:reference id="someService" version="0.0.1"
protocol="dubbo"
interface="com.abc.service.SomeService"/>

<!--指定消费0.0.2版本,即newService提供者-->
<dubbo:reference id="someService" version="0.0.2"
protocol="rmi"
interface="com.abc.service.SomeService"/>

多Provider时候consumer的负载均衡策略

负载均衡算法可以在消费者端指定,也可以在提供者端指定:

  • 若消费者与提供者均设置了负载均衡策略,消费者端设置的优先级高。
  • 若消费者端没有显式的设置,但提供者端显式的设置了,且**同一个服务(接口名、版本号、分组都相同)**的负载均衡策略相同。消费者调用时会按照提供者设置的策略调用。
  • 若多个提供者端设置的不相同,则最后一个注册的会将前面注册的信息覆盖。

对应负载均衡策略有:

  • random:随机算法,是 Dubbo 默认的负载均衡算法。存在服务堆积问题。
  • roundrobin:轮询算法。按照设定好的权重依次进行调度。
  • leastactive:最少活跃度调度算法。即被调度的次数越少,其优选级就越高,被调度到的机率就越高。
  • consistenthash:一致性 hash 算法。对于相同参数的请求,其会被路由到相同的提供者。

负载均衡算法可以在服务上指定,就会在服务的所有方法生效,也可以在服务的方法上生效,就会在每一个方法上单独生效

1
2
3
<dubbo:service interface="com.abc.service.SomeService"
loadbalance="roundrobin"
ref="someService"/>
1
2
3
4
5
6
<dubbo:service interface="com.abc.service.SomeService"
ref="someService">
<dubbo:method name="m1" loadbalance="roundrobin"/>
<dubbo:method name="m2" loadbalance="leastactive"/>
<dubbo:method name="m3" loadbalance="random"/>
</dubbo:service>
1
2
3
<dubbo:reference id="someService"
interface="com.abc.service.SomeService"
loadbalance="roundrobin"/>
1
2
3
4
5
6
<dubbo:reference id="someService" 
interface="com.abc.service.SomeService">
<dubbo:method name="m1" loadbalance="roundrobin"/>
<dubbo:method name="m2" loadbalance="leastactive"/>
<dubbo:method name="m3" loadbalance="random"/>
</dubbo:reference>

集群容错(消费者端的逻辑)

当消费者调用提供者集群时发生异常的处理方案。容错策略可以设置在消费者端,也可以设置在提供者端。若消费者与提供者均做了设置,则消费者端的优先级更高。

  • Failover:故障转移策略。调用失败会自动尝试调用其它服务器。通常用于读操作。
  • Failfast:快速失败策略。消费者端只发起一次调用,若失败则立即报错。通常用于非幂等性的写操
  • Failsafe:失败安全策略。当消费者调用提供者出现异常时,直接忽略本次消费操作。不太重要的任务,例如,写入审计日志等操作。
  • Failback:失败自动恢复策略。消费者调用提供者失败后,Dubbo 会记录下该失败请求,然后定时自动重新发送该请求。该策略通常用于实时性要求不太高的服务,例如消息通知操作。
  • Forking:并行策略。消费者对于同一服务并行调用多个提供者服务器,只要一个成功即调用结束并返回结果。通常用于实时性要求较高的读操作,但其会浪费较多服务器资源。
  • Broadcast:广播策略。广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。

如果使用的是默认的策略 Failover,则可以指定重试次数,注意设置的是重试次数,不含第一次正常调用:

1
2
3
4
<dubbo:service interface="com.abc.service.SomeService"
ref="oldService" version="0.0.1"
retries="2"
protocol="rmi, dubbo"/>
1
2
3
4
5
6
<dubbo:service interface="com.abc.service.SomeService"
ref="someService">
<dubbo:method name="m1" loadbalance="roundrobin" retries="2"/>
<dubbo:method name="m2" loadbalance="leastactive"/>
<dubbo:method name="m3" loadbalance="random"/>
</dubbo:service>

指定容错策略指定是服务级别的,不能是方法级别的:

1
2
3
<dubbo:service interface="com.abc.service.SomeService"
ref="someService"
cluster="failfast"/>

使用Mock机制实现服务降级

当服务器压力剧增的情况下,根据当前降低服务级别,以释放服务器资源,保证核心任务的正常运行。例如,双 11 时 0 点-2 点期间淘宝用户不能修改收货地址,不能查看历史订单,就是典型的服务降级。

能够实现服务降级方式很多:

  • 部分服务暂停:页面能够访问,但是部分服务暂停服务,不能访问。
  • 部分服务延迟:页面可以访问,当用户提交某些请求时系统会提示该操作已成功提交给了服务器,由于当前服务器繁忙,此操作随后会执行。在等待了若干时间后最终用户可以看到正确的执行结果。
  • 全部服务暂停:系统入口页面就不能访问,提示由于服务繁忙此服务暂停。跳转到一个预先设定好的静态页面。
  • 随机拒绝服务:服务器会按照预先设定好的比例,随机挑选用户,对其拒绝服务。作为用户,其看到的就是请重试。可能再重试就可获得服务。

dubbo 使用的Mock降级处理机制:

  • Mock Null:
1
2
3
4
5
<!-- 有返回值的方法降级结果是null,没有返回值的方法降级结果是无任何显示 -->
<dubbo:reference id="userService"
mock="return null"
check="false"
interface="com.abc.service.UserService"/>
  • Class Mock:在业务接口所在的包中定义一个类,该类的命名需要满足:业务接口简单类名 + Mock。
1
2
3
4
<dubbo:reference id="userService" 
mock="true"
check="false"
interface="com.abc.service.UserService"/>
1
2
3
4
5
6
7
8
9
10
11
12
public class UserServiceMock implements UserService {

@Override
public String getUsernameById(int id) {
return "没有该用户:" + id;
}

@Override
public void addUser(String username) {
System.out.println("添加该用户失败:" + username);
}
}

服务调用超时设置

前面的服务降级的发生,其实是由于消费者调用服务超时引起的

即从发出调用请求到获取到提供者的响应结果这个时间超出了设定的时限。默认服务调用超时时限为 1 秒。可以在消费者端与提供者端设置超时时限。

1
2
<dubbo:service interface="com.abc.service.UserService"
ref="userService" timeout="3000"/>

服务限流

  • 直接限流:通过对连接的数量直接限制来达到限流的目的。超过限制则会让再来的请求等待,直到等待超时,或获取到相应服务(官方方案)。
  • 间接限流:通过一些非连接数量设置的间接手段来达到限流的目的(个人经验)。

executes 限流:仅提供者

该属性仅能设置在提供者端。可以设置为接口级别,也可以设置为方法级别。对指定服 务(方法)的连接数量进行限制。

executes="10" 在接口上指定就是接口的每一个方法并发执行数不能超过10个。

accepts 限流:仅提供者

该属性仅可设置在提供者端的<dubbo:provider/><dubbo:protocol/>,是针对指定协议的连接数进行限制。

actives 限流:两端均可

该限流方式与前两种不同的是,其可以设置在提供者端,也可以设置在消费者端。可以设置为接口级别,也可以设置为方法级别。

提供者端:

根据客户端与服务端建立的连接是长连接还是短连接,其意义不同: 

长连接:当前这个服务上的一个长连接最多能够处理的请求个数。对长连接数量没有限制。 

短连接:当前这个服务上可以同时处理的短连接数量。

消费者端:

根据客户端与服务端建立的连接是长连接还是短连接,其意义不同: 

长连接:当前这个消费者的一个长连接最多能够提交的请求个数。对长连接数量没有限制。

短连接:当前这个消费者可以同时提交的短连接数量。

connections 限流:两端均可

可以设置在提供者端,也可以设置在消费者端。限定连接的个数。

一般情况下,我们使用的都是默认的服务暴露协议 Dubbo,所以,一般会让 connections 与 actives 联用。connections 限制长连接的数量,而 actives 限制每个长连接上的请求数量。

lazy 延迟连接

消费者真正调用提供者方法时才创建长连接。 仅可设置在消费者端,且不能设置为方法级别。仅作用于 Dubbo 服务暴露协议。用于减少长连接数量。

当前消费者所有接口的方法发出连接都采用延迟连接

1
<dubbo:consumer lazy="true"/>
1
2
3
<dubbo:reference id="userService"
lazy="true"
interface="com.abc.service.UserService"/>

粘连连接

让所有客户端要访问的同一接口的同一方法,尽可能是的由同一Inovker 提供服务。其用于限定流向。

粘连连接仅能设置在消费者端,其可以设置为接口级别,也可以设置为方法级别。仅作用于 Dubbo 服务暴露协议。用于减少长连接数量。粘连连接的开启将自动开启延迟连接。

1
2
3
<dubbo:reference id="userService"
sticky="true"
interface="com.abc.service.UserService"/>

声明式缓存

为了进一步提高消费者对用户的响应速度,减轻提供者的压力,Dubbo 提供了基于结果的声明式缓存。该缓存是基于消费者端的,所以使用很简单,只需修改消费者配置文件,与提供者无关。该缓存是缓存在消费者端内存中的,一旦缓存创建,即使提供者宕机也不会影响消费者端的缓存。

1
2
3
<dubbo:reference id="someService"  
cache="true"
interface="com.abc.service.SomeService"/>

默认可以缓存 1000 个结果。若超出 1000,将采用 LRU 策略来删除缓存,以保证最热的数据被缓存。注意,该删除缓存的策略不能修改。

指定多注册中心以及仅订阅和仅注册

同一个服务注册到不同的中心,使用逗号进行分隔。

对于消费者工程,用到哪个注册中心了,就声明哪个注册中心,无需将全部注册中心进行声明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--声明注册中心-->
<dubbo:registry id="bjCenter" address="zookeeper://bjZK:2181"/> <!--北京中心-->
<dubbo:registry id="shCenter" address="zookeeper://shZK:2181"/> <!--上海中心-->
<dubbo:registry id="gzCenter" address="zookeeper://gzZK:2181"/> <!--广州中心-->
<dubbo:registry id="cqCenter" address="zookeeper://cqZK:2181"/> <!--重庆中心-->

<!--注册Service实现类-->
<bean id="weixinService" class="com.abc.provider.WeixinServiceImpl"/>
<bean id="zhifubaoService" class="com.abc.provider.ZhifubaoServiceImpl"/>

<!--暴露服务:同一个服务注册到不同的中心;不同的服务注册到不同的中心-->
<dubbo:service interface="com.abc.service.SomeService"
ref="weixinService" group="pay.weixin" register="bjCenter, shCenter"/>
<dubbo:service interface="com.abc.service.SomeService"
ref="zhifubaoService" group="pay.zhifubao" register="gzCenter, cqCenter"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--声明注册中心-->
<dubbo:registry id="bjCenter" address="zookeeper://bjZK:2181"/>
<dubbo:registry id="gzCenter" address="zookeeper://gzZK:2181"/>
<dubbo:registry id="cqCenter" address="zookeeper://cqZK:2181"/>

<!--指定调用bjCenter注册中心微信服务-->
<dubbo:reference id="weixin" group="pay.weixin" registry="bjCenter"
interface="com.abc.service.SomeService"/>

<!--指定调用gzCenter与cqCenter注册中心支付宝服务-->
<dubbo:reference id="gzZhifubao" group="pay.zhifubao" registry="gzCenter"
interface="com.abc.service.SomeService"/>
<dubbo:reference id="cqZhifubao" group="pay.zhifubao" registry="cqCenter"
interface="com.abc.service.SomeService"/>

1
2
3
4
5
<!--声明注册中心:仅订阅-->
<dubbo:registry id="gzCenter" address="zookeeper://gzZK:2181" register="false"/>
<!--声明注册中心:仅注册-->
<dubbo:registry id="gzCenter" address="zookeeper://gzZK:2181" subscribe="false"/>

服务暴露延迟

如果我们的服务启动过程需要 warmup 事件,就可以使用 delay 进行服务延迟暴露。

在服务提供者的<dubbo:service/>标签中添加 delay 属性。其值可以有三类:

  • 正数:单位为毫秒,表示在提供者对象创建完毕后的指定时间后再发布服务。
  • 0:默认值,表示当前提供者创建完毕后马上向注册中心暴露服务。 
  • -1:表示在 Spring 容器初始化完毕后再向注册中心暴露服务。

消费者异步调用与提供者异步执行

异步调用一般应用于提供者提供的是耗时性 IO 服务。

Futrue 与 CompletableFuture 的区别与联系

对于消费者不用获取提供者所调用的耗时操作结果的情况,使用 Future 与 CompletableFuture 效果是区别不大的。但对于需要获取返回值的情况,Future源自于 JDK5,通过 Future 的 get()获取返回结果,get()方法会阻塞轮询,CompletableFuture 源自于 JDK8,Dubbo2.7.0 之后使用 Future 实现消费者对提供者的异步调用。通过 CompletableFutrue 的回调获取返回结果,不会发生阻塞,好用。

使用 Future 实现

画板

提供方接口定义:

1
2
3
4
5
6
public interface OtherService {
String doFirst();
String doSecond();
String doThird();
String doFourth();
}

消费方调用定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 异步调用
String result1 = service.doThird();
System.out.println("调用结果1 = " + result1);
Future<String> thirdFuture = RpcContext.getContext().getFuture();

String result3 = service.doFourth();
System.out.println("调用结果3 = " + result3);
Future<String> fourFuture = RpcContext.getContext().getFuture();

// 阻塞
String result2 = thirdFuture.get();
System.out.println("调用结果2 = " + result2);
String result4 = fourFuture.get();
System.out.println("调用结果4 = " + result4);

需要在消费方的XML中声明为异步的,注意此时的服务端还是在同步执行的:

1
2
3
4
5
<dubbo:reference id="otherService"  timeout="20000"
interface="com.abc.service.OtherService" >
<dubbo:method name="doThird" async="true"/>
<dubbo:method name="doFourth" async="true"/>
</dubbo:reference>

使用 CompletableFuture