-
服务导出
- 接口级服务注册
- 应用级服务注册
-
服务引入
- 接口级服务引入
- 应用级服务引入
- MigrationInvoker的生成
-
@EnableDubbo -> @DubboComponentScan -> DubboComponentScanRegistrar -> DubboSpringInitializer#initialize -> DubboDeployApplicationListener -> ContextRefreshedEvent -> DefaultModuleDeployer#start -> 服务导出与导入
-
在做完服务导出与服务引入后,还会做几件非常重要的事情
- 导出一个应用元数据服务(就是一个MetadataService服务,这个服务也会注册到注册中心),或者将应用元数据注册到元数据中心
- 生成当前应用的实例信息对象ServiceInstance,比如应用名、实例ip、实例port,并将实例信息注册到注册中心,也就是应用级注册
服务导出
- 只要扫描到了@DubboService,就会解析对应的类,得到服务相关的配置信息
- 把这些配置信息封装成为一个ServiceConfig对象,并调用其export()进行服务导出
- 此时一个ServiceConfig对象就表示一个Dubbo服务
- 服务导出,主要就是完成三件事情
- 确定服务的最终参数配置
- 按不同协议启动对应的Server(服务暴露)
- 将服务注册到注册中心(服务注册)
dubbo.application.register-mode
- instance:表示只进行应用级注册
- interface:表示只进行接口级注册
- all:表示应用级注册和接口级注册都进行,默认
接口级服务注册
- 当确定好了最终的服务配置后,Dubbo就会根据这些配置信息生成对应的服务URL
tri://192.168.65.221:20880/org.apache.dubbo.springboot.demo.DemoService?application=dubbo-springboot-demo-provider&timeout=3000
- 服务消费者只要能获得到这个服务URL,就知道了关于这个Dubbo服务的全部信息,包括服务名、支持的协议、ip、port、各种配置
- 在
RegistryProtocol#export
方法中,服务URL存在了Zookeeper的/dubbo/接口名/providers
目录下 - 如果利用动态配置功能修改了服务的参数,就要重新生成服务URL并重新注册到注册中心
- key是接口名,value就是服务URL
应用级服务注册
- 为了降低注册中心的压力,Dubbo3.0支持了应用级注册,同时也兼容接口级注册
- 只记录应用所对应的实例信息(IP+绑定的端口),而不关心一个应用中到底有多少个Dubbo服务
- 注册中心存储的数据变少了,注册中心中数据的变化频率变小了
- 使得 Dubbo3 能实现与异构微服务体系如Spring Cloud等在地址发现层面更容易互通
- 消费者根据接口名找到所对应的应用名
- 在进行服务导出的过程中,会在Zookeeper中存一个映射关系,在服务导出的最后一步(
ServiceConfig#exported
)会保存这个映射关系:接口名:应用名
- 这个映射关系存在Zookeeper的
/dubbo/mapping
目录下
- 在进行服务导出的过程中,会在Zookeeper中存一个映射关系,在服务导出的最后一步(
- 消费者需要从服务提供者的元数据服务获取服务的配置信息
- 在应用启动过程中会进行服务导出和服务引入,然后就会暴露一个应用元数据服务
- 这个应用元数据服务就是一个Dubbo服务(Dubbo框架内置的)
- 消费者可以调用这个服务来获取某个应用中所提供的所有Dubbo服务以及服务配置信息
- 根据所配置的注册中心生成两个URL
- registry 代表接口级服务注册
- service-discovery-registry 表应用级服务注册
ServiceDiscoveryRegistry
在注册一个服务URL时(应用级服务注册)- 并不会往注册中心存数据,而只是把服务URL存到到一个MetadataInfo对象中
- MetadataInfo对象保存了当前应用中所有Dubbo服务信息(服务名、支持的协议、绑定的端口、timeout等)
- 在应用启动的最后,才会进行应用级注册
- 应用的名字
- 获取应用元数据的方式
- 当前实例的ip和port
- 当前实例支持哪些协议以及对应的port
- 把实例信息存入注册中心的
/services/应用名
目录下 - 如何确定实例的ip和端口:一个实例上可能支持多个协议以及多个端口
- 获取MetadataInfo对象中保存的所有服务URL,优先取dubbo协议对应ip和port
- 没有dubbo协议则所有服务URL中的第一个URL的ip和port
- 一般一个协议一般只会对应一个端口
- 如果对应了多个:最终存入endpoint中的会保证一个协议只对应一个端口,另外那个将被忽略
- 元数据服务:
dubbo.metadata.storage-type
默认 local- 如果为local,那就会启用应用元数据服务,最终服务消费者就会调用元数据服务获取到应用元数据信息
- 如果为remote,那就不会暴露应用元数据服务,那么服务消费者从元数据中心获取应用元数据
- 元数据中心可以用Zookeeper来实现,在暴露完元数据服务之后,在注册实例信息到注册中心之前,就会把MetadataInfo存入元数据中心
- 记录了当前实例上提供了哪些服务以及对应的协议,注意并没有保存对应的端口
- endpoint中记录了协议对应的端口
- 元数据中心是集中式的,元数据服务式分散在各个提供者实例中的
- 应用级服务注册的流程
- 在导出某个Dubbo服务URL时,会把服务URL存入MetadataInfo中
- 导出完某个Dubbo服务后,就会把服务接口名:应用名存入元数据中心(可以用Zookeeper实现)
- 导出所有服务后,完成服务引入后
- 判断要不要启动元数据服务,如果要就进行导出,固定使用Dubbo协议
- 将MetadataInfo存入元数据中心
- 确定当前实例信息(应用名、ip、port、endpoint)
- 将实例信息存入注册中心,完成应用注册
- 服务暴露:根据不同的协议启动不同的Server
- 比如dubbo和tri协议启动的都是Netty
- Dubbo2.7中的http协议启动的就是Tomcat
服务引入
- 利用@DubboReference注解来引入某一个Dubbo服务
- 应用在启动过程中,进行完服务导出之后,就会进行服务引入
接口级服务引入
- 服务消费者会利用接口名从注册中心找到该服务接口所有的服务URL
- 服务消费者会根据每个服务URL的protocol、ip、port生成对应的Invoker对象
- 比如生成TripleInvoker、DubboInvoker等
- 调用这些Invoker的invoke()方法就会发送数据到对应的ip、port
- 生成好所有的Invoker对象之后,就会把这些Invoker对象进行封装并生成一个服务接口的代理对象
- 代理对象调用某个方法时,会把所调用的方法信息生成一个Invocation对象
- 并最终通过某一个Invoker的invoke()方法把Invocation对象发送出去
- 所以代理对象中的Invoker对象是关键,服务引入最核心的就是要生成这些Invoker对象
- Invoker是非常核心的一个概念,也有非常多种类
- TripleInvoker:表示利用tri协议把Invocation对象发送出去
- DubboInvoker:表示利用dubbo协议把Invocation对象发送出去
- ClusterInvoker:有负载均衡功能
- MigrationInvoker:迁移功能,Dubbo3.0新增的
- 像TripleInvoker和DubboInvoker对应的就是具体服务提供者,包含了服务提供者的ip地址和端口
- 会负责跟对应的ip和port建立Socket连接,后续就可以基于这个Socket连接并按协议格式发送Invocation对象
- 接口级服务引入核心就是要找到当前所引入的服务有哪些服务URL,然后根据每个服务URL生成对应的Invoker
- 根据当前引入的服务接口生成一个RegistryDirectory对象,表示动态服务目录
- 用来查询并缓存服务提供者信息
- RegistryDirectory对象会根据服务接口名去注册中心查找所有的服务URL
- 比如Zookeeper中的
/dubbo/服务接口名/providers/
节点
- 比如Zookeeper中的
- 根据每个服务URL生成对应的Invoker对象,并把Invoker对象存在RegistryDirectory对象的invokers属性中
- RegistryDirectory对象也会监听
/dubbo/服务接口名/providers/
节点的数据变化- 一旦发生了变化就要进行相应的改变
- 最后将RegistryDirectory对象生成一个ClusterInvoker对象
- 到时候调用ClusterInvoker对象的invoke()方法就会进行负载均衡选出某一个Invoker进行调用
- 根据当前引入的服务接口生成一个RegistryDirectory对象,表示动态服务目录
应用级服务引入
- Dubbo中找到应用的实例地址还远远不够
- 因为直接使用的接口,所以在Dubbo中最终还是得找到服务接口有哪些服务提供者
- 在进行应用级注册时是按照一个协议对应一个port存的
- 但是接口级服务一个协议可以对应多个port
- 服务消费者端寻找服务URL的逻辑更复杂了
- 根据当前引入的服务接口生成一个ServiceDiscoveryRegistryDirectory对象,表示动态服务目录
- 用来查询并缓存服务提供者信息
- 根据接口名去获取
/dubbo/mapping/服务接口名
节点的内容,拿到的就是该接口所对应的应用名 - 有了应用名之后,再去获取
/services/应用名
节点下的实例信息 - 依次遍历每个实例,每个实例都有一个编号revision
- 根据metadata-type进行判断
- 如果是local:则调用实例上的元数据服务获取应用元数据(MetadataInfo)
- 如果是remote:则根据应用名从元数据中心获取应用元数据(MetadataInfo)
- 获取到应用元数据之后就进行缓存,key为revision,MetadataInfo对象为value
- 为什么要去每个实例上获取应用的元数据信息?
- 因为有可能不一样,虽然是同一个应用,但是在运行不同的实例的时候,可以指定不同的参数,比如不同的协议,不同的端口,虽然在生产上基本不会这么做,但是Dubbo还是支持了这种情况
- 根据metadata-type进行判断
- 根据从所有实例上获取到的MetadataInfo以及endpoint信息,就能知道所有实例上所有的服务URL
一个接口+一个协议+一个实例 : 对应一个服务URL
- 拿到了这些服务URL之后,就根据当前引入服务的信息进行过滤
- 会根据引入服务的接口名+协议名
- 消费者可以在@DubboReference中指定协议,表示只使用这个协议调用当前服务
- 如果没有指定协议,那么就会去获取tri、dubbo、rest这三个协议对应的服务URL
- Dubbo3.0默认只支持这三个协议
- 经过过滤之后,就得到了当前所引入的服务对应的服务URL了
- 根据每个服务URL生成对应的Invoker对象
- 并把Invoker对象存在ServiceDiscoveryRegistryDirectory对象的invokers属性中
- 最后将ServiceDiscoveryRegistryDirectory对象生成一个ClusterInvoker对象
- 到时候调用ClusterInvoker对象的invoke()方法就会进行负载均衡选出某一个Invoker进行调用
- 根据当前引入的服务接口生成一个ServiceDiscoveryRegistryDirectory对象,表示动态服务目录
MigrationInvoker的生成
- 接口级服务引入和应用级服务引入,最终都是得到某个服务对应的服务提供者Invoker
dubbo.application.service-discovery.migration
- FORCE_INTERFACE,强制使用接口级服务引入
- FORCE_APPLICATION,强制使用应用级服务引入
- APPLICATION_FIRST,智能选择是接口级还是应用级,默认就是这个
- 在进行某个服务的服务引入时
- 会统一利用
InterfaceCompatibleRegistryProtocol#refer
来生成一个MigrationInvoker对象
- 会统一利用
- MigrationInvoker中有三个属性
- invoker 用来记录接口级 ClusterInvoker
- serviceDiscoveryInvoker 用来记录应用级的 ClusterInvoker
- currentAvailableInvoker 用来记录当前使用的 ClusterInvoker,要么是接口级,要么应用级
- 一开始构造出来的MigrationInvoker对象中三个属性都为空
- 接下来会利用MigrationRuleListener来处理MigrationInvoker对象,也就是给这三个属性赋值
- 在MigrationRuleListener的构造方法中,会从配置中心读取DUBBO_SERVICEDISCOVERY_MIGRATION组下面的"当前应用名+.migration"的配置项,配置项为yml格式,对应的对象为MigrationRule,也就是可以配置具体的迁移规则,比如某个接口或某个应用的MigrationStep(FORCE_INTERFACE、APPLICATION_FIRST、FORCE_APPLICATION)
- 如果没有配置迁移规则,则会看当前应用中是否配置了migration.step
- 如果没有,那就从全局配置中心读取dubbo.application.service-discovery.migration来获取MigrationStep
- 如果也没有配置,那MigrationStep默认为APPLICATION_FIRST
- 如果没有配置迁移规则,则会看当前应用中是否配置了migration.threshold,如果没有配,则threshold默认为-1
- 表示一个阈值,比如配置为2,表示应用级Invoker数量是接口级Invoker数量的两倍时才使用应用级Invoker,不然就使用接口级数量
- 如果没有配置迁移规则,则会看当前应用中是否配置了migration.step
dubbo:
application:
name: dubbo-springboot-demo-consumer
parameters:
migration.step: FORCE_APPLICATION
migration.threshold: 2
- 确定了step和threshold之后,就要真正开始给MigrationInvoker对象中的三个属性赋值了,先根据step调用不同的方法
switch (step) {
case APPLICATION_FIRST:
// 先进行接口级服务引入得到对应的ClusterInvoker,并赋值给invoker属性
// 再进行应用级服务引入得到对应的ClusterInvoker,并赋值给serviceDiscoveryInvoker属性
// 再根据两者的数量判断到底用哪个,并且把确定的ClusterInvoker赋值给 currentAvailableInvoker属性
migrationInvoker.migrateToApplicationFirstInvoker(newRule);
break;
case FORCE_APPLICATION:
// 只进行应用级服务引入得到对应的ClusterInvoker
// 并赋值给serviceDiscoveryInvoker和currentAvailableInvoker属性
success = migrationInvoker.migrateToForceApplicationInvoker(newRule);
break;
case FORCE_INTERFACE:
default:
// 只进行接口级服务引入得到对应的ClusterInvoker
// 并赋值给invoker和 currentAvailableInvoker属性
success = migrationInvoker.migrateToForceInterfaceInvoker(newRule);
}
- 得到了接口级ClusterInvoker和应用级ClusterInvoker之后,就会利用DefaultMigrationAddressComparator来进行判断
- 如果应用级ClusterInvoker中没有具体的Invoker,那就表示只能用接口级Invoker
- 如果接口级ClusterInvoker中没有具体的Invoker,那就表示只能用应用级Invoker
- 如果应用级ClusterInvoker和接口级ClusterInvoker中都有具体的Invoker,则获取对应的Invoker个数
- 如果在迁移规则和应用参数中都没有配置threshold,那就读取全局配置中心的dubbo.application.migration.threshold参数,如果也没有配置,则threshold默认为0(不是-1了)
- 用应用级Invoker数量 / 接口级Invoker数量,得到的结果如果大于等于threshold,那就用应用级ClusterInvoker,否则用接口级ClusterInvoker
- threshold默认为0,那就表示在既有应用级Invoker又有接口级Invoker的情况下,就一定会用应用级Invoker
- 两个正数相除,结果肯定为正数
- 当然你自己可以控制threshold,如果既有既有应用级Invoker又有接口级Invoker的情况下
- 想在应用级Invoker的个数大于接口级Invoker的个数时采用应用级Invoker,那就可以把threshold设置为1,表示个数相等,或者个数相除之后的结果大于1时用应用级Invoker,否者用接口级Invoker
- 这样MigrationInvoker对象中的三个数据就能确定好值了,和在最终的接口代理对象执行某个方法时,就会调用MigrationInvoker对象的invoke
- 在这个invoke方法中会直接执行currentAvailableInvoker对应的invoker的invoker方法
- 从而进入到了接口级ClusterInvoker或应用级ClusterInvoker中
- 从而进行负载均衡,选择出具体的DubboInvoer或TripleInvoker
- 完成真正的服务调用
导入导出流程图:
https://www.processon.com/view/link/62c441e80791293dccaebded