Dubbo的基础与高级应用
- 负载均衡、集群容错、服务降级
- 本地存根、本地伪装、异步调用
负载均衡
Dubbo一共支持四种策略:
轮询
- 轮询,按公约后的权重设置轮询比率。
- 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
- 如果A, B, C 服务的权重相同, 则会ABCABC的顺序调用下去;
- 如果A= 1/2, B=1/4, C=1/4服务的权重不相同,则AABCAABC的循序调用下去;
随机
- 随机,按权重设置随机概率。
- 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
- A, B, C的权重相同, 则A,B, C的调用率 平均为1/3,调用顺序是无序的;
- A= 1/2, B=1/4, C=1/4服务的权重不相同, 则A的调用率为1/2, BC为1/4, 调用顺序时无序的
一致性哈希
- 一致性 Hash,相同参数的请求总是发到同一提供者。
- 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
注意时时相同参数: 使用一致性哈希,调用相同接口时, 传相同参数(获取ID = 1的记录)请求, 则会一致把请求发给同一个服务器;
最少活跃数调用
-
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。 -
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。 -
A,B, C服务器同时都在处理1个请求, 那么活跃数都是1, 一段时间后, 只有A的请求处理完, 活跃数-1, 活跃数为0; 那么新的请求发送过来,让A去处理,A的活跃数最小,为0, BC为1; -
逻辑
- 消费者会缓存所调?服务的所有提供者,?如记为p1、p2、p3三个服务提供者,每个提供者内都有? 个属性记为active,默认位0
- 消费者在调?次服务时,如果负载均衡策略是leastactive
- 消费者端会判断缓存的所有服务提供者的active,选择最?的,如果都相同,则随机
- 选出某?个服务提供者后,假设位p2,Dubbo就会对p2.active+1
- 然后真正发出请求调?该服务
- 消费端收到响应结果后,对p2.active-1
- 这样就完成了对某个服务提供者当前活跃调?数进?了统计,并且并不影响服务调?的性能
使用
@Reference注解的loadbalance属性配置(全是小写), 也可以通过管理台配置;
@Reference(version = "default", loadbalance = "consistenthash")
private DemoService demoService;
问题1: 为什么负载统计放在了客户端Consumer,而不是服务提供方Provider?
原因: 无法统计/难以统计;一台机器无法知道另一台机器的活跃数, 可以通过HTTP请求到其他服务器去拿活跃数, 这明显不显示, 如果集群1000台, 那么就需要发送一千次HTTP请求, 耗费时间直接以秒为单位, 不可行;
问题2:为什么负载统计放在了客户端Consumer,而不是注册中心?
原因: 类似问题1, 你每次处理请求,都需要告诉注册中心, 活跃数+1, 处理完请求,活跃数-1, 相当于两次HTTP请求,耗费时间。还有一个原因, 每台机器处理请求都得和注册中心进行交互, 哪怕是集群注册中心, 也扛不住这么大的并发量;
服务超时
在服务提供者和服务消费者上都可以配置服务超时时间,这两者是不一样的。
消费者调用一个服务,分为三步:
- 消费者发送请求(网络传输)
- 服务端执行服务
- 服务端返回响应(网络传输)
如果在服务端和消费端只在其中一方配置了timeout,那么没有歧义,
- 配置在消费方表示消费端调用服务的超时时间,消费端如果超过时间还没有收到响应结果,则消费端会抛超时异常,
- 配置在服务端,服务端不会抛异常,服务端在执行服务后,会检查执行该服务的时间,如果超过timeout,则会打印一个超时日志。服务会正常的执行完。
如果在服务端和消费端各配了一个timeout,那就比较复杂了,假设
- 服务执行为5s
- 消费端timeout=3s
- 服务端timeout=6s
那么消费端调用服务时,消费端会收到超时异常(因为消费端超时了),服务端一切正常执行完, 打印超时日志(服务端没有超时)。
超时配置
- 客户端配置
@Reference注解的timeout 属性配置, 也可以通过管理台配置;
@Reference(version = "timeout", timeout = 3000)
private DemoService demoService;
- 管理台设置
通过@Service注解的timeout属性配置,也可以通过管理台配置;
@Service(version = "timeout", timeout = 4000)
public class TimeoutDemoService implements DemoService {
@Override
public String sayHello(String name) {
System.out.println("执行了timeout服务" + name);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行结束" + name);
URL url = RpcContext.getContext().getUrl();
return String.format("%s:%s, Hello, %s", url.getProtocol(), url.getPort(), name);
}
}
集群容错
集群调用失败时,Dubbo 提供的容错方案; 服务消费者在调用某个服务时,这个服务有多个服务提供者,在经过负载均衡后选出其中一个服务提供者之后进行调用,但调用报错后,Dubbo所采取的后续处理策略。
failover 重试
默认情况下是failover 重试, 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries=“2” 来设置重试次数(不含第一次)。 默认是2次;
- 服务端设置
@Service(cluster = "failover" , retries = 2)
public class DemoServiceImpl implements DemoInterface {
@Override
public User getUser() {
return new User("zqh");
}
}
- 消费端设置
@Reference(cluster = "failover" ,retries = 2)
private DemoInterface demoService;
Failfast Cluster快速失败
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
- 消费端设置:
@Reference(cluster = "failfast")
- 服务端设置
@Service(cluster = "failfast")
Failsafe Cluster失败安全
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
- 消费端设置:
@Reference(cluster = "failsafe ")
- 服务端设置
@Service(cluster = "failsafe ")
Failback Cluster失败自动恢复
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
- 消费端设置:
@Reference(cluster = "failback ")
- 服务端设置
@Service(cluster = "failback ")
Forking Cluster并行调用
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=“2” 来设置最大并行数。
- 消费端设置:
@Reference(cluster = "forking")
- 服务端设置
@Service(cluster = "forking")
Broadcast Cluster广播逐个调用
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
-
通过 broadcast.fail.percent 配置节点调用失败的比例,当达到这个比例后,BroadcastClusterInvoker 将不再调用其他节点,直接抛出异常。 -
broadcast.fail.percent 取值在 0~100 范围内。默认情况下当全部调用失败后,才会抛出异常。 -
broadcast.fail.percent 只是控制的当失败后是否继续调用其他节点,并不改变结果(任意一台报错则报错)。 -
broadcast.fail.percent 参数 在 dubbo2.7.10 及以上版本生效。 -
broadcast.fail.percent=20 代表了当 20% 的节点调用失败就抛出异常,不再调用其他节点。
@Reference(cluster = "broadcast")
- 服务端设置
@Service(cluster = "broadcast")
集群容错成熟度
服务降级
服务消费者在调用某个服务提供者时,如果该服务提供者报错了,所采取的措施。
集群容错和服务降级的区别在于:
- 集群容错是整个集群范围内的容错
- 服务降级是单个服务消费者/ 单个服务提供者的自身容错
使用
- 消费端
@Reference(mock = "fail:return+null")
- 服务端
@Service(mock = "fail:return+null")
都表示当服务调用失败后, 返回为null;
本地伪装
就是mock, 本质和服务降级一样; 使用场景:一些未开发完成的功能需要mock一些数据, 就是假数据返回;
- 服务提供者
@Service(mock = "force:return+null")
表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
本地存根
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑; 比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub 1,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
使用
package com.dubbo.consumer;
import com.tuling.DemoService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import java.io.IOException;
@EnableAutoConfiguration
public class StubDubboConsumerDemo {
@Reference(version = "timeout", timeout = 1000, stub = "true")
private DemoService demoService;
public static void main(String[] args) throws IOException {
ConfigurableApplicationContext context = SpringApplication.run(StubDubboConsumerDemo.class);
DemoService demoService = context.getBean(DemoService.class);
System.out.println((demoService.sayHello("周瑜")));
}
}
- @Reference(version = “timeout”, timeout = 1000, stub = “true”)
Dubbo根据包名,类名+Stub方式生成类路径去找类信息, 即com.dubbo.DemoServiceStub, 招不到报错;需要提供一个构造函数,构造参数为接口类类型,用来传入真正的远程代理对象;最终通过这个对象再去调用服务; - @Reference(version = “timeout”, timeout = 1000, stub = “com.tuling.DemoServiceStub”)
package com.dubbo;
public class DemoServiceStub implements DemoService {
private final DemoService demoService;
public DemoServiceStub(DemoService demoService){
this.demoService = demoService;
}
@Override
public String sayHello(String name) {
try {
return demoService.sayHello(name);
} catch (Exception e) {
return "容错数据";
}
}
}
异步调用
从 2.7.0 开始,Dubbo 的所有异步编程接口开始以 CompletableFuture 为基础
基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。 接口方法:
public interface AsyncService {
CompletableFuture<String> sayHello(String name);
}
消费者:
@EnableAutoConfiguration
public class AsyncDubboConsumerDemo {
@Reference(version = "async")
private DemoService demoService;
public static void main(String[] args) throws IOException {
ConfigurableApplicationContext context = SpringApplication.run(AsyncDubboConsumerDemo.class);
DemoService demoService = context.getBean(DemoService.class);
CompletableFuture<String> future = demoService.sayHelloAsync("异步调用");
future.whenComplete((v, t) -> {
if (t != null) {
t.printStackTrace();
} else {
System.out.println("Response: " + v);
}
});
System.out.println("结束了");
}
}
提供者:
@Service(version = "async")
public class AsyncDemoService implements DemoService {
@Override
public String sayHello(String name) {
System.out.println("执行了同步服务" + name);
URL url = RpcContext.getContext().getUrl();
return String.format("%s:%s, Hello, %s", url.getProtocol(), url.getPort(), name);
}
@Override
public CompletableFuture<String> sayHelloAsync(String name) {
System.out.println("执行了异步服务" + name);
return CompletableFuture.supplyAsync(() -> {
return sayHello(name);
});
}
}
Java8中新推出的异步编程, 会异步编程, 看这个就很简单。
Dubbo中的REST
官网Rest说明 注意Dubbo的REST也是Dubbo所支持的一种协议。
当我们用Dubbo提供了一个服务后,如果消费者没有使用Dubbo也想调用服务,那么这个时候我们就可以让我们的服务支持REST协议,这样消费者就可以通过REST形式调用我们的服务了。
注意:如果某个服务只有REST协议可用,那么该服务必须用@Path注解定义访问路径
使用
@Service
@Path("demo")
public class RestDemoService implements DemoService {
@GET
@Path("say")
@Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8})
@Override
public String sayHello(@QueryParam("name") String name) {
System.out.println("执行了rest服务" + name);
URL url = RpcContext.getContext().getUrl();
return String.format("%s: %s, Hello, %s", url.getProtocol(), url.getPort(), name);
}
}
类似SpringMVC,学到这的人 不会SpringMVC的(我无语凝咽)
其他
- 动态配置
官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/config-rule/ 通过管理台去修改服务的配置, 进而不用重启服务器; - 服务路由
官网地址:http://dubbo.apache.org/zh/docs/v2.7/user/examples/routing-rule/ 作用类同网关的作用(一般是架构师整的) - 管理台
github地址:https://github.com/apache/dubbo-admin
小结
Dubbo应用很多, 官网起码几十种了,学不完的,学下重点的东西。
|