文章内容输出来源:拉勾教育Java高薪训练营
一、介绍
Dubbo是一款高性能、轻量级的Java RPC框架(官网地址)
提供了三大核心能力
- 面向接口的远程方法调用
- 智能容错和负载均衡
- 服务自动注册和发现
二、处理流程
2.1 节点说明
- Provider:服务提供方,暴露服务
- Consumer:服务消费者,调用远程服务
- Registry:注册中心,服务注册与发现
服务注册中心在整个流程中扮演了至关重要的角色。Dubbo推荐使用ZooKeeper
- Monitor:监控中心,统计服务的调用次数和调用时间
- Container:服务运行容器,启动、加载、运行服务提供者
2.2 调用关系说明
- 实线是同步调用
- 虚线是异步调用
- 蓝色虚线是在程序执行中的功能
- 紫色虚线是在启动时完成的功能
2.3 流程步骤
1.Provider部署在一个Container,启动后向Registry注册自己提供的服务
2. Consumer启动后,向Registry订阅自己所需要的服务
3. Registry返回Provider的地址列表给Consumer
4. Consumer从地址列表中,基于负载均衡算法,选择一个Provider进行调用
5. Provider和Consumer将运行情况(调用次数、时间)定时发送给Monitor进行统计
如果Provider的地址信息变更,Registry会基于长连接推送变更数据给Consumer
Consumer调用Provider的服务失败,则会重新选择一个Provider进行调用
三、管理控制台
dubbo-admin,可以对服务管理、路由规则、服务降级、负载均衡等这些功能进行监控
-
下载项目
git地址:https://github.com/apache/dubbo-admin -
修改项目中的dubbo.properties文件
dubbo.registry.address=zookeeper://zk所在机器ip:zk端口
dubbo.admin.root.password=root
dubbo.admin.guest.password=guest
- 项目打包,运行jar包
四、配置参数
4.1 dubbo:application
对应dubbo中的
ApplicationConfig
,代表当前应用的信息
- dubbo.application.name: 应用名称
- dubbo.application.owner: 应用的负责人
- dubbo.application.qosEnable: 是否启动QoS,默认是true
- dubbo.application.qosPort:启动QoS绑定的端口,默认为22222
- dubbo.application.qosAcceptForeignIp:是否允许远程访问,默认是false
QoS是Quanlity of Service,通过一些命令来动态对服务进行查询和控制,如对服务的上下线
4.2 dubbo:registry
对应dubbo中的
RegistryConfig
,代表模块使用的注册中心
- dubbo.registry.id:注册中心标识ID,有多个注册中心通过此字段进行区别
- dubbo.registry.address:注册中心的访问地址
- dubbo.registry.protocol:注册中心使用的协议是什么
- dubbo.registry.timeout:超时限制,当与注册中心不在同一个机房,会将此参数延长
4.3 dubbo:protocol
对应dubbo中的
ProtocolConfig
,指定服务进行数据传输时所使用的协议
- dubbo.protocol.id:协议的标识ID,有多个协议使用时可以区别
- dubbo.protocol.name:指定协议名称,默认是dubbo
4.4 dubbo:service
对应dubbo中的
ServiceConfig
,用于指定当前需要对外暴露的服务信息
- dubbo.service.interface:指定当前需要对外暴露的接口是什么
- dubbo.service.ref:具体实现对应的引用。一般是在Spring的容器中进行托管,所以一般这里也指的是Spring的Bean ID
- dubbo.service.version:对外暴露的版本号。消费者在消费的时候只会根据固定的版本号进行消费。
4.5 dubbo:reference
对应dubbo中的
ReferenceConfig
,做为消费者的配置
- dubbo.reference.id:服务对应的BeanId
- dubbo.reference.interface:服务接口名
- dubbo.reference.version:指定当前服务的版本,与服务提供者的版本一致
- dubbo.reference.registry:指定使用的注册中心地址的标识ID
4.6 dubbo:method
对应dubbo的
MethodConfig
,指定具体方法级别在RPC操作时的配置
- dubbo.method.name:指定方法名称
- dubbo.method.async:是否异步,默认为false
五、SPI
SPI是JDK提供的服务提供发现机制。有不少的框架使用它做了扩展,使用这个SPI机制的好处是能实现解耦。
它的实现步骤是:
- 提供标准服务接口
//service_api,创建接口
public interface HelloService {
String sayHello();
}
- 提供相关接口实现
//service_api_impl,创建接口实现类
public class HumanHelloService implements HelloService {
public String sayHello() {
return "Hello baby";
}
}
需要在项目的resources下创建
META-INF/services/com.yyh.service.HelloService
文件,内容为实现类的全限定名
com.yyh.service.impl.HumanHelloService
- 调用者通过SPI机制进行接口调用
通过java.util.ServiceLoader动态装载实现模块,它会去扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM
//service_main, 调用者实现
public static void main(String[] args) {
ServiceLoader<HelloService> services = ServiceLoader.load(HelloService.class);
for (HelloService service : services) {
System.out.println(service.getClass().getName());
System.out.println(service.sayHello());
}
}
六、Dubbo SPI
Dubbo中大量的使用SPI来做为扩展点,通过实现同一接口的前提下,可以进行定制自己的实现类。像负载均衡、常用的协议等,都可以通过SPI的方式进行定制化.Dubbo中已经存在很多这种扩展点
6.1 扩展点运用
- 在
service_api
创建服务接口
@SPI("human")
public interface HelloService {
String sayHello();
}
使用@SPI注解标识这是一个扩展点接口,@SPI注解中的值表示默认调用的接口实现类
- 在
service_api_impl
创建服务接口实现类
public class HumanHelloService implements HelloService {
public String sayHello() {
return "Hello baby";
}
}
public class DogHelloService implements HelloService {
public String sayHello() {
return "wang wang!";
}
}
在
resources
中创建META-INF/dubbo/com.yyh.service.HelloService
文件,内容是实现类的全限定名
com.yyh.service.impl.DogHelloService
com.yyh.service.impl.HumanHelloService
- 在
service_main
实现调用者调用
借助ExtensionLoader获取扩展加载器,然后遍历所有的扩展点进行输出
public class ClientMain {
public static void main(String[] args) {
//获取扩展加载器
ExtensionLoader<HelloService> extensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class);
Set<String> supportedExtensions = extensionLoader.getSupportedExtensions();
//遍历所有的支持的扩展点
for (String supportedExtension : supportedExtensions) {
String result = extensionLoader.getExtension(supportedExtension).sayHello();
System.out.println(result);
}
}
}
6.2 Adaptive功能
Adaptive功能,主要解决的问题是如何动态的选择具体的扩展点。
- 通过
getAdaptiveExtension
统一对指定接口对应的所有扩展点进行封装 - 通过
URL
的方式对扩展点来进行动态选择
它的使用步骤如下:
- 在服务接口中的方法使用@Adaptive注解进行声明,并增加URL参数
@SPI("human")
public interface HelloService {
@Adaptive
String sayHello(URL url);
}
- 在服务接口实现类中实现上述的接口方法
- 在调用者端,调用时需要传入指定的URL参数,并且在参数中指明要调用的具体的实现类参数
public class ClientAdaptiveMain {
public static void main(String[] args) {
//通过URL的方式对扩展点来进行动态选择
//参数hello.service表示HelloService接口,后面的值是HumanHelloService的human。如果没有设置则跳用@SPI的value值所指定的实现
URL url = URL.valueOf("test://localhost/hello?hello.service=");
//通过getAdptiveExtension提供一个统一的类来对所有的扩展点提供支持
HelloService extension = ExtensionLoader.getExtensionLoader(HelloService.class).getAdaptiveExtension();
String msg = extension.sayHello(url);
System.out.println(msg);
}
}
七、Dubbo的过滤机制
过滤器Filter拦截对象是服务提供方和服务消费方。每次远程方法执行时,该拦截都会被执行。这提供了强大的扩展性,可以很方便去实现一些像ip限流、日志、监控的功能。
7.1 自定义过滤器实现
- 创建拦截类,实现
org.apache.dubbo.rpc.Filter
接口 - 使用@Activate注解对类进行注册,通过group指定拦截的是生产端/消费端
@Activate(group = {CommonConstants.CONSUMER, CommonConstants.PROVIDER})
public class DubboFilter implements Filter {
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
long start = System.currentTimeMillis();
try {
return invoker.invoke(invocation);
}finally {
System.out.println("invoke time:" + (System.currentTimeMillis() - start));
}
}
}
- 在
resources
中创建META-INF/dubbo/org.apache.dubbo.rpc.Filter
文件,内容为拦截类的信息
timeFilter=com.yyh.filter.DubboFilter
7.2 自定义过滤器的使用
一般类似于这样的功能都是单独开发依赖的,只要在第三方的项目中引入依赖,配置上相应的过滤器,在调用接口时,该方法便会自动拦截
八、负载均衡策略
负载均衡, 就是将请求分摊到多个操作单元上进行执行,从而共同完成工作任务。
负载均衡策略主要用于客户端,存在多个提供者时进行选择某个提供者。
Dubbo 提供了多种均衡策略(包括随机、轮询、最少活跃调用数、一致性Hash),缺省为random随机调用。
8.1 如何配置
既可以在服务提供者一方配置,也可以在服务消费者一方配置
8.2 自定义负载均衡器
- 创建一个maven项目
dubbo-spi-loadbalance
- 创建自定义类
org.apache.dubbo.rpc.cluster.LoadBalance
,实现LoadBalance
的SPI接口 - 在类中实现选取所有机器中第一个(按字母排序+端口排序)
- 在
resources
中的META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance
文件中将当前类的信息写入
九、异步调用
当提供者接口响应需要很久,那消费者就可以使用异步调用模式进行调用,然后使用Future去获取异步调用响应回来的结果。
9.1 异步调用的实现
-
消费端配置异步调用
-
通过 RpcContext.getContext().getFuture() 来进行获取Future对象来进行后续的结果等待操作
十、线程池
dubbo在使用时,都是通过创建真实的业务线程池进行操作的。
目前的两个线程池模型
- fix
创建固定大小的线程池。也是Dubbo默认的使用方式,默认创建的执行线程数为200,并且是没有任何等待队列的。所以再极端的情况下可能会存在问题,比如某个操作大量执行时,可能存在堵塞的情况。
- cache
创建非固定大小的线程池,当线程不足时,会自动创建新的线程。但是使用这种的时候需要注意,如果突然有高TPS的请求过来,方法没有及时完成,则会造成大量的线程创建,对系统的CPU和负载都是压力,执行越多反而会拖慢整个系统
十一、服务动态降级
高峰期流量涌来,服务器压力剧增,可以对一些服务进行有策略的降低服务级别,以释放服务器资源,保证核心任务的正常运行。
使用服务降级,可以防止分布式服务发生雪崩效应
什么是雪崩?就是蝴蝶效应,当一个请求发生超时,一直等待着服务响应,那么在高并发情况下,很多请求都是因为这样一直等着响应,直到服务资源耗尽产生宕机,而宕机之后会导致分布式其他服务调用该宕机的服务也会出现资源耗尽宕机,这样下去将导致整个分布式服务都瘫痪,这就是雪崩。
11.1 服务降级实现方式
- 在 dubbo 管理控制台配置服务降级
- 屏蔽
# 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可
用时对调用方的影响
mock=force:return+null
- 容错
# 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳
定时对调用方的影响
mock=fail:return+null
- 指定返回简单值或者null