RPC是协议
既然是协议就只是一套规范,那么就需要有人遵循这套规范来进行实现。目前典型的RPC实现包括:Dubbo、Thrift、GRPC、Hetty等。
网络协议和网络IO透明
既然RPC的客户端认为自己是在调用本地对象。那么传输层使用的是TCP/UDP还是HTTP协议,者是一些其他的网络协议它就不需要关心了。既然网络协议对其透明,那么调用过程中,使用的是哪一种网络IO模型调用者也不需要关心。
信息格式对其透明
在本地应用程序中,对象调用需要传递一些参数,会返回一个调用结果。对象内部是如何使用这些参数,并计算出处理结果的,调用方是不需要关心的。那么对于RPC来说,这些参数会以某种信息格式传递给网络上的另外一台计算机,这个信息格式是怎样构成的,调用方是不需要关心的。
有跨语言能力
调用方实际上也不清楚远程服务器的应用程序是使用什么语言运行的。那么对于调用方来说,无论服务器方使用的是什么语言,本次调用都应该成功,并且返回值也应该按照调用方程序语言所能理解的形式进行描述。
### 3.1、xml配置
我们在配置使用了
<dubbo:application name="dubbo-core" />
<dubbo:protocol id="dubbo" name="dubbo" />
<dubbo:registry address="zookeeper://182.92.189.235:2181" />
<dubbo:protocol name="dubbo" port="20881" />
<dubbo:annotation package="com.xiu.dubbo.service" />
涉及到自定义标签配置:spring标签解析逻辑、spring自定义标签解析
标签 | 描述 |
---|---|
用于配置应用信息,dubbo计算其依赖关系 | |
用于配置应用模块信息(适用于一个应用多模块) | |
用于配置服务提供者的访问协议 | |
用于配置注册中心 Multicast、Zookeeper、Redis、Dubbo四种注册中心 | |
用于配置服务提供者的通用配置值,provider是原始的服务提供方式:配置参数超级多,比较繁琐,其上的配置为默认可以被所有的 |
|
用于配置服务提供者暴露自己的服务 | |
用于配置服务消费者的默认值,即dubbo:reference标签的默认值,同 |
|
用于配置服务消费者引用服务 | |
支持注解扫描的方式注册service服务和引用服务 | |
dubbo监控模块配置 |
spring标签解析:org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
学习一个框架 需要了解骨架(核心逻辑),细节方面我们先当做黑盒处理
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
//xml的doc对象获取其中命名空间 http\://code.alibabatech.com/schema/dubbo
String namespaceUri = this.getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
} else {
//最终根据命名空间获取到DubboNamespaceHandler对象去进行解析 parse方法
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
//省略异常处理
} else {
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
}
}
DubboNamespaceHandler类 注册了两个BeanDefinitionParser (将对应的标签通过parse转换为BeanDefinition 最终进行实例化对象放入spring容器中)spring源码分析之bean的创建
类 | 说明 |
---|---|
DubboBeanDefinitionParser | 用于解析application、module、server、reference等 |
AnnotationBeanDefinitionParser | 用于解析annotation标签 注解扫描 |
class对象 | 配置属性信息 |
---|---|
ApplicationConfig =====> |
id、name |
ProtocolConfig =====> |
id、name、port |
RegistryConfig =====> |
id、address |
ServiceBean =====> |
id、name、timeout、interface、ref |
ReferenceBean =====> |
id、name、interface |
ServiceBean和ReferenceBean两者都实现了InitializingBean接口的afterPropertiesSet方法 该方法会在对应的bean实例化完后的初始化被调用
需要在pom中整合dubbo-spring-boot-starter 从而支持自动装配,spring容器启动过程中会扫描所有XXXx-starter中的spring.factories文件加载所有的自动配置类
装配bean实例 | 作用 |
---|---|
@EnableDubboConfig | 该注解通过引入DubboConfigConfigurationRegistrar去进行注册DubboConfigConfiguration.Single/Multiple 将dubbo相关配置填充到AbstractConfig中 |
RelaxedDubboConfigBinder | 具体用来进行属性绑定的实现类(可自定义扩展) |
ServiceAnnotationBeanPostProcessor | 扫描包下所有使用@Service的类转换为ServiceBean的BeanDefinition |
ReferenceAnnotationBeanPostProcessor | 扫描包下所有使用@Reference的类转换为ReferenceBean的BeanDefinition |
EnableDubboConfig 将除了ServiceBean和ReferenceBean的所有AbstractConfig转换为BeanDefinition(用于后面实例化)
public void exportService(){
//模拟spring服务实现(此处不使用spring环境 读者可以自行使用)
UserService demoService = new UserServiceImpl();
//1、创建应用信息(服务提供者和服务消费者均需要,以便用于计算应用之间的依赖关系)
ApplicationConfig appliction = new ApplicationConfig("demo-core");
//2、创建注册中心(服务提供者和服务消费者均需要,以便用于服务注册和服务发现)
//dubbo支持的注册中心:①simple、②Multicast、③zookeeper、④ redis
//这里采用zookeeper(常用也是dubbo推荐使用的)
RegistryConfig registry = new RegistryConfig();
registry.setAddress("zookeeper://182.92.189.235:2181");
//3、创建服务暴露后对应的协议信息,该设置约定了消费者使用哪种协议来请求服务提供者
//dubbo消费协议如下: ①dubbo://、②hessian://、③rmi://、 ④http://、⑤webservice://、⑦memcached://、⑧redis://
ProtocolConfig protocol = new ProtocolConfig();
//使用dubbo协议
protocol.setName("dubbo");
protocol.setPort(28830);
//4、该类为服务消费者的全局配置(比如是否延迟暴露,是否暴露服务等)
ProviderConfig provider = new ProviderConfig();
//是否暴露服务
provider.setExport(true);
//延迟一秒发布服务
provider.setDelay(1000);
//5、服务提供者 会配置应用、注册、协议、提供者等信息
ServiceConfig<UserService> serviceService = new ServiceConfig<>();
//设置应用信息
serviceService.setApplication(appliction);
//设置注册信息
serviceService.setRegistry(registry);
//设置相关协议
serviceService.setProtocol(protocol);
//设置全局配置信息
serviceService.setProvider(provider);
//设置接口信息
serviceService.setInterface(UserService.class);
serviceService.setRef(demoService);
//6、服务暴露 (最终上面设置的相关信息转换为Dubbo URL的形式暴露出去)
serviceService.export();
while (true){
}
}
public void consumerDubboService(){
//声明应用 dubbo生态质检服务调用是基于应用的
ApplicationConfig application = new ApplicationConfig("dubbo-refrence");
//涉及注册中心
RegistryConfig registryCenter = new RegistryConfig();
registryCenter.setAddress("zookeeper://182.92.189.235:2181");
//消费者消费
//设置消费者全局配置
ConsumerConfig consumerConfig = new ConsumerConfig();
//设置默认的超时时间
consumerConfig.setTimeout(1000*5);
ReferenceConfig<UserService> userConfigReference = new ReferenceConfig<>();
userConfigReference.setApplication(application);
// List registryConfigs = new ArrayList<>();
// registryConfigs.add(registryCenter);
userConfigReference.setRegistry(registryCenter);
userConfigReference.setInterface(UserService.class);
//dubbo直连
//userConfigReference.setUrl("dubbo://xxx.xxx.xx:22890");
//设置methodConfig 方法级别的dubbo参数包配置 比如方法名必填、重试次数、超时时间、负载均衡策略
MethodConfig methodConfig = new MethodConfig();
//方法名必填
methodConfig.setName("queryUserInfo");
//超时时间
methodConfig.setTimeout(1000 * 5);
//重试次数
methodConfig.setRetries(3);
//获取服务(并非真实的对象而是代理对象)
UserService userService = userConfigReference.get();
//调用对象方法
String info = userService.queryUserInfo();
System.out.println(info);
}
config** 配置层**:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类
proxy 服务代理层:服务接口透明代理,生成动态代理 扩展接口为 ProxyFactory
registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService
cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance
monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService
protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter
exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec
serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool
### 7.1、export()方法
public synchronized void export() {
//从provider获取两个属性 export(是否已经发布过)
// delay:延迟发布 延迟调用任务做发布
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
if (export != null && !export) {
return;
}
if (delay != null && delay > 0) {
delayExportExecutor.schedule(new Runnable() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
} else {
doExport();
}
}
延迟部署应用场景:
缓存预热:当我们的暴露服务需要依赖一些静态数据,这些静态数据是通过加载数据库或者文件然后缓存到应用内存中。此时我们可以通过在服务延迟暴露的时间段内进行缓存预加载。
依赖资源:假设我们对外提供的服务需要依赖另外一个服务,而另外一个服务的暴露时间比较缓慢,那么我们就可以把当前对外暴露的服务进行延迟暴露,这样就可以减少当调用依赖服务时出现超时异常的情况。
protected synchronized void doExport() {
//1、非法校验
//2、配置的优先级 从做到右优先级依次递减 provider --> module --> application
//泛化接口
if (ref instanceof GenericService) {
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
generic = Boolean.TRUE.toString();
}
} else {
//真实接口
try {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
//检查接口class和对应的方法配置(interfaceClass是否为接口,method级别的配置是否在接口中包含对应的方法)
checkInterfaceAndMethods(interfaceClass, methods);
//ref最终dubbo服务业务逻辑
checkRef();
//设置非泛化
generic = Boolean.FALSE.toString();
}
//已经不使用了 local
// stub 本地
if (stub != null) {
if ("true".equals(stub)) {
stub = interfaceName + "Stub";
}
Class<?> stubClass;
try {
stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
//这里要求stub需要实现接口
if (!interfaceClass.isAssignableFrom(stubClass)) {
//抛出异常
}
}
//检查并填充 application、registry、protocol、Service
checkApplication();
checkRegistry();
checkProtocol();
appendProperties(this);
checkStub(interfaceClass);
checkMock(interfaceClass);
if (path == null || path.length() == 0) {
path = interfaceName;
}
//将对应的ServiceBean中的所有参数转换为DubboURL
doExportUrls();
//服务提供者包装成ProviderModel存放到集合
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
1、发布相关参数校验
2、获取配置信息 配置的优先级 从做到右优先级依次递减 serice --> provider --> module --> application
3、获取服务接口Class属性 其中涉及泛化接口和具体接口
4、dubbo的local(本地调用)、stub(存根)、mock(异常降级)相关class属性
5、检查并填充相关dubbo配置,比如应用信息、注册信息。协议信息、服务提供者信息
6、生成Dubbo URL并发布
7、服务提供者包装成ProviderModel存放到集合
一般情况下,服务提供者需要暴露出接口和方法,所以项目开发中服务提供者和服务发布者会共同引入一个xxx-api的接口,服务调用者不关注接口和方法可以使用dubbo提供的泛化接口这里只需要知道一个接口全限定名以及入参和返参的情况下,1、降低业务之间耦合性,2、提供一个通用的方式来调用服务 通过一种通用的方式去调用 dubbo 的分布式服务。
泛化接口使用该类GenericService 其只有一个接口$invoke()
/**
* method 方法名
* parameterTypes 参数类型列表
* args 参数列表
*/
Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException;
应用场景:
dubbo泛化调用网关设计 (网关一侧一般不依赖 接口类 但是后端请求dubbo接口,这里就需要使用dubbo的泛化接口)
通用的dubbo测试框架(MOCK) 不需要关注所有的dubbo接口,此处也是使用泛化接口合适。dubbo-postman平台
不同语言开发的服务调用(php 请求javadubbo接口)
控制是否调用服务端dubbo服务,比如远程调用之前参数校验以及日志打印等,远程调用之后一些业务逻辑实现以及调用过程中的异常处理。类似于AOP的around advice。 可以在服务端实现 相当于所有消费者都具有的默认行为,也可以在消费端实现 表示消费端自己行为,如果消费方实现存根,则服务方 存根 将不起作用。
//消费端配置stub
<dubbo:reference id="userService" interface="com.xiu.dubbo.service.UserService"
stub="com.xiu.dubbo.service.UserServiceStub"
/>
实现类:
//实现对应接口所有方法
public class UserServiceStub implements UserService {
private final UserService userService;
/**
* 需要提供构造函数(方便服务端传递代理对象proxy)
* @param userService
*/
public UserServiceStub(UserService userService) {
this.userService = userService;
}
@Override
public String sayHello(String name) {
System.out.println("消息端模拟调用服务之前逻辑处理");
String result = userService.sayHello(name);
System.out.println("消费端模拟调用服务之后逻辑处理");
return result;
}
}
mock详细用法
服务提供者出错时(抛出 RpcException)超时,网络不稳定的降级处理
进行 mock 调用;可以用于本地测试
<dubbo:reference id="userService" interface="com.xiu.dubbo.service.UserService"
mock="com.xiu.dubbo.service.UserServiceMock"/>
实现类:
//实现接口所有方法
public class UserServiceMock implements UserService{
@Override
public String queryUserInfo() {
return "queryUserInfo服务降级";
}
@Override
public String sayHello(String name) {
return "sayHello服务降级";
}
}
//...省略将ServiceConfig相关参数转换为DUBBO URL的相关步骤
//服务发布
//第一个参数:ref具体的服务实现
//第二个参数:服务接口
//完整的DUBBURL 包含注册中心 前缀
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
//包装成wrapperInvoker 将serviceConfig作为其metadata
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
//服务发布 通过dubbo的spi机制进行对应的发布
Exporter<?> exporter = protocol.export(wrapperInvoker);
registry://172.16.2.207:2181/com.alibaba.dubbo.registry.RegistryService?
application=dubbo-core
&dubbo=2.0.2
&pid=19404
®istry=zookeeper
×tamp=1632818916276
$&export=duboo://协议
dubbo://10.1.6.235:20880/com.xiu.dubbo.service.DemoService
?anyhost=true
&application=dubbo-core
&bean.name=com.xiu.dubbo.service.DemoService
&bind.ip=10.1.6.235
&bind.port=20880
&cluster=failover
&dubbo=2.0.2&
generic=false
&interface=com.xiu.dubbo.service.DemoService
&methods=queryDemoInfo
&pid=10304
&side=provider
&timeout=3000
×tamp=1632820237700
属性配置 | 属性说明 |
---|---|
dubbo:// | dubbo服务提供者使用的服务协议 有dubbo、http等 |
192.168.1.16:28830/ | 服务ip和端口 |
com.xiu.dubbo.service.UserService | 服务名 |
?anyhost=true | 是否被所有ip消费者使用 |
&application=demo-core | 应用名 |
&bind.ip=192.168.1.16 &bind.port=28830 | 服务所在的服务器地址和发布的端口 |
&default.delay=1000 | 默认延迟暴露时间 |
&delay=1000 | 自定义设置的延迟暴露时间 |
&dubbo=2.6.2 | dubbo版本 |
&generic=false | 是否泛化接口 |
&interface=com.xiu.dubbo.service.UserService | 接口类 |
&methods=queryUserInfo | 接口方法列表 |
&pid=53692 | pid |
&side=provider | 服务类型 提供者provider 消费者 Consumer |
×tamp=1621766013477 | 时间戳 |
//根据DUBBO的URL获取对应的协议头 dubbo:// 或者 registry://
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
//根据extName 获取对应的实现类 dubbo对应 DubboProtocol registry 对应RegistryProtocol
extension = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);
其中会获取export属性中的执行执行dubbo协议相关的逻辑
com.alibaba.dubbo.registry.integration.RegistryProtocol#doLocalExport
RegistryProtocol:以zk作为注册中心为例:最终调用ZookeeperRegistry的doRegister 创建临时节点, doSubscribe 通知所有监控该节点的变化的(新建节点则为创建监听)。
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
//前缀是dubbo//
URL url = invoker.getUrl();
//1、创建DubboExporter放入缓存exporterMap 对于stub类型数据 需要额外添加stubServiceMethodsMap
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);
Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
if (isStubSupportEvent && !isCallbackservice) {
String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
if (logger.isWarnEnabled()) {
logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}
} else {
stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
}
}
//进行服务发布 主要是选用netty、Mina通信协议建立对应的Server 比如NettyServer、MinaServer
openServer(url);
//添加dubbo更高效的序列化机制Kryo, FST
optimizeSerialization(url);
return exporter;
}
DubboProtocol#openServer
-->DubboProtocol#createServer ip:port 对应Server(netty服务端)
--->HeaderExchanger#bind
---> new HeaderExchangeServer(Transporters.bind(url, "编解码处理器"));
---->com.alibaba.dubbo.remoting.transport.netty4.NettyTransporter#bind
----> 创建netty服务
com.alibaba.dubbo.common.extension.ExtensionLoader#createExtension方法
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
private void init() {
//如果该实例已经初始化 则返回
if (initialized) {
return;
}
//标记该实例初始化(先占坑) 防止重复实例化
initialized = true;
//消费接口非空校验
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException(" interface not allow null!");
}
//获取全局服务消费者配置信息(ConsumerConfig) 逻辑:获取该对象配置的ConsumerConfig对象 并将系统属性中
//涉及到服务消费者dubbo.consumer.xxx 相关的配置属性通过setXxx方法填充到ConsumerConfig对象中
checkDefault();
//服务消费者dubbo.reference.xxx 相关的配置属性通过setXxx方法填充到ReferenceConfig对象中
appendProperties(this);
//设置是否泛化接口标识
if (getGeneric() == null && getConsumer() != null) {
setGeneric(getConsumer().getGeneric());
}
//如果是泛化接口 class为GenericService
if (ProtocolUtils.isGeneric(getGeneric())) {
interfaceClass = GenericService.class;
} else {
try {
//普通接口 反射获取class
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
//检查接口对应的class和method级别的参数配置
//所谓方法级别的配置可以针对接口中的的某个方法进行 超时、重试次数、负载均衡策略等的设置
checkInterfaceAndMethods(interfaceClass, methods);
}
//获取直连dubbo对应的url
//1、项目运行的时候jdk 命令启动 带参数java -D com.xxx.dubbo.DemoService=dubbo://localhost:28090
//2、直连配置多的时候 用-Ddubbo.resolve.file指定映射文件路径 java -Ddubbo.resolve.file=xxx.properties 在xxx.properties中(key为服务名,value为服务提供者url)
//3、xml配置直连
String resolve = System.getProperty(interfaceName);
String resolveFile = null;
if (resolve == null || resolve.length() == 0) {
resolveFile = System.getProperty("dubbo.resolve.file");
if (resolveFile == null || resolveFile.length() == 0) {
File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
if (userResolveFile.exists()) {
resolveFile = userResolveFile.getAbsolutePath();
}
}
if (resolveFile != null && resolveFile.length() > 0) {
Properties properties = new Properties();
FileInputStream fis = null;
try {
fis = new FileInputStream(new File(resolveFile));
properties.load(fis);
} catch (IOException e) {
throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e);
} finally {
try {
if (null != fis) fis.close();
} catch (IOException e) {
logger.warn(e.getMessage(), e);
}
}
resolve = properties.getProperty(interfaceName);
}
}
if (resolve != null && resolve.length() > 0) {
url = resolve;
if (logger.isWarnEnabled()) {
if (resolveFile != null && resolveFile.length() > 0) {
logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service.");
} else {
logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service.");
}
}
}
//根据优先级从ConsumerConfig、ModuleConfig、ApplicationConfig获取相应的配置信息
//比如注册中心、监控信息等
if (consumer != null) {
if (application == null) {
application = consumer.getApplication();
}
if (module == null) {
module = consumer.getModule();
}
if (registries == null) {
registries = consumer.getRegistries();
}
if (monitor == null) {
monitor = consumer.getMonitor();
}
}
if (module != null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {
monitor = module.getMonitor();
}
}
if (application != null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) {
monitor = application.getMonitor();
}
}
//获取application(应用)相关的信息填充application对象中
//还会添加两个 有关dubbo(优雅)停机操作属性
checkApplication();
//添加mock、local、stub相关配置class
checkStubAndMock(interfaceClass);
//获取side、version、时间戳、pid、revision、methods、
//application应用信息、module模块、consumer消费者全局配置、method级别的配置等信息添加到map中
// (用与生成服务消费相关的Dubbo URL)
Map<String, String> map = new HashMap<String, String>();
Map<Object, Object> attributes = new HashMap<Object, Object>();
map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
if (!isGeneric()) {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put("revision", revision);
}
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("NO method found in service interface " + interfaceClass.getName());
map.put("methods", Constants.ANY_VALUE);
} else {
map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
map.put(Constants.INTERFACE_KEY, interfaceName);
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, consumer, Constants.DEFAULT_KEY);
appendParameters(map, this);
String prefix = StringUtils.getServiceKey(map);
if (methods != null && !methods.isEmpty()) {
for (MethodConfig method : methods) {
appendParameters(map, method, method.getName());
String retryKey = method.getName() + ".retry";
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
map.put(method.getName() + ".retries", "0");
}
}
appendAttributes(attributes, method, prefix + "." + method.getName());
checkAndConvertImplicitConfig(method, map, attributes);
}
}
//DUBBO_IP_TO_REGISTRY 属性可以显示设置服务消费者需要注册到注册中心的地址
// (docker服务部署服务消费者可能会出现注册不是真实消费者的注册地址)
String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
if (hostToRegistry == null || hostToRegistry.length() == 0) {
//没有配置显示注册地址则获取宿主机真实地址
hostToRegistry = NetUtils.getLocalHost();
} else if (isInvalidLocalHost(hostToRegistry)) {
throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
}
map.put(Constants.REGISTER_IP_KEY, hostToRegistry);
//添加MethodConfig相关的属性信息到系统上下文中
StaticContext.getSystemContext().putAll(attributes);
//创建代理对象
ref = createProxy(map);
//将对应代理对象包装成ConsumerModel 存放到ApplicationModel对应的集合中
ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}
1、该实例重复和接口为空校验
2、从系统变量获取属性设置到consumerConfig全局配置和ReferenceConfig中
3、获取接口class属性(1、普通接口:需要检查接口对应的class和method级别的参数配置 2泛化接口:GenericService)
4、获取直连dubbo 对应的服务接口
获取方式又分成
项目运行的时候jdk 命令启动 带参数
java -D com.xxx.dubbo.DemoService=dubbo://localhost:28090
服务多的时候 直连配置多的时候 用-Ddubbo.resolve.file指定映射文件路径
java -Ddubbo.resolve.file=xxx.properties
在xxx.properties中(key为服务名,value为服务提供者url)
xml配置直连
api编码:userConfigReference.setUrl(“dubbo://xxx.xxx.xx:22890”)
注解: @Reference(url = “dubbo;//xxxxxx.xx:22200”)
5、以consumerConfig -> moduleConfig -> applicationConfig的优先级顺序获取注册中心、监控等信息
6、获取side、version、时间戳、pid、revision、methods、application应用信息、
module模块、consumer消费者全局配置、method级别的配置等信息添加到map中
7、applicationConfig 配置信息填充
8、local、stub、mock相关class配置
9、获取宿主地址信息(可以显示配置ip)
10、添加MethodConfig相关的属性信息到系统上下文中
method级别的配置涉及到dubbo接口的同步异步调用 https://blog.csdn.net/qq_32523587/article/details/87826839
11、创建代理对象
12、将对应代理对象包装成ConsumerModel 存放到ApplicationModel对应的集合中
private T createProxy(Map<String, String> map) {
//判断是不是dubbo的本地调用 如果是本地调用则不发起远程服务,只进行本地关联 但执行 Dubbo 的 Filter 链
//从 2.2.0 开始,每个服务默认都会在本地暴露。在引用服务的时候,默认优先引用本地服务
//
//
//
//
//
//
//
URL tmpUrl = new URL("temp", "localhost", 0, map);
final boolean isJvmRefer;
if (isInjvm() == null) {
if (url != null && url.length() > 0) {
// if a url is specified, don't do local reference
isJvmRefer = false;
} else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
// by default, reference local service if there is
isJvmRefer = true;
} else {
isJvmRefer = false;
}
} else {
isJvmRefer = isInjvm().booleanValue();
}
//如果是本地调用获取本地调用的invoker JVM内部调用
if (isJvmRefer) {
URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
invoker = refprotocol.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
} else {
//直连调用逻辑
if (url != null && url.length() > 0) {
// user specified URL, could be peer-to-peer address, or register center's address.
String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
if (us != null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
if (url.getPath() == null || url.getPath().length() == 0) {
url = url.setPath(interfaceName);
}
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
} else {
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else {
//走注册中心
//获取多个注册中心以及注册中心对应的监控中心 遍历将注册中心和监控中心添加到Dubbo URL 集合中 后面创建Invoker对象用
//获取多个注册中心
List<URL> us = loadRegistries(false);
if (us != null && !us.isEmpty()) {
for (URL u : us) {
//获取注册中心对应的监控中心
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
//添加到DubboURL集合中 该Dubbo URL 是注册中心转换的url
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
}
}
if (urls == null || urls.isEmpty()) {
throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config to your spring config.");
}
}
//注册中心单个则使用该注册中心创建对应的Invoker
if (urls.size() == 1) {
invoker = refprotocol.refer(interfaceClass, urls.get(0));
} else {
//多个注册中心遍历处理 每一个注册中心创建一个Invoker添加到invokers集合中
//最终选则最新的注册地址
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
invokers.add(refprotocol.refer(interfaceClass, url));
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; // use last registry url
}
}
if (registryURL != null) {
// registry url is available
// use AvailableCluster only when register's cluster is available
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
invoker = cluster.join(new StaticDirectory(u, invokers));
} else {
// not a registry url
invoker = cluster.join(new StaticDirectory(invokers));
}
}
}
//省略日志打印
//使用ProxyFacrory创建dubbo服务对应的代理对象
return (T) proxyFactory.getProxy(invoker);
}
创建代理对象主要分成三大部分
(Invoker类是Dubbo核心模型对象,dubbo进行rpc调用的具体调用执行类,是其dubbo核心基础、对于服务方它是服务调用提供者,对于消费方它是服务的远程调用)
调用refprotocol.refer(interfaceClass, DUBBO URL) 获取到invoker对象
创建RegistryDirectory(注册中心目录,服务注册和服务发现在该对象中实现,以zk为例子 服务注册在zk上创建对应的节点,服务发现是添加subscribe方法监听zk对应的目录上的节点变化)
其中其通过RegistryDirectory的notify方法 监听providers目录下的所有服务提供者Invoker 会将其中的DUBBO URL转换为Invoker
代理工厂根据获取到的invoker创建代理对象(JavassistProxyFactory实例)
同时有一个包装类StubProxyFactoryWrapper对使用stub模式进行处理
MockClusterInvoker ==》FailoverClusterInvoker ==》DubboInvoker 链条的doInvoker方法
获取invoker先是根据registry协议调用RegistryProtocol的doRefer()方法
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo-refrence&dubbo=2.0.2&pid=18472&refer=application=dubbo-refrence&dubbo=2.0.2&interface=com.xiu.dubbo.service.UserService&methods=queryUserInfo,sayHello&pid=18472®ister.ip=10.1.6.235&side=consumer×tamp=1634111002722®istry=zookeeper×tamp=1634111007008
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
//将registry:// 转换为zookeeper: (这里以zk为注册中心)
//url由registry://xxxx:xx 转换为zookeeper://xxxx:xx
url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
//获取注册中心服务 ZookeeperRegistry实现
Registry registry = registryFactory.getRegistry(url);
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
// group="a,b" or group="*"
//获取指定分组中的同一个服务响应数据合并返回
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
String group = qs.get(Constants.GROUP_KEY);
if (group != null && group.length() > 0) {
if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
|| "*".equals(group)) {
return doRefer(getMergeableCluster(), registry, type, url);
}
}
//普通获取服务远程调用对象invoker
return doRefer(cluster, registry, type, url);
}
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
//见名知意 注册目录服务对象(服务注册和服务发现是该对象的主要功能)
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
//设置zk注册中心服务
directory.setRegistry(registry);
//设置协议服务RegistryProtocol
directory.setProtocol(protocol);
//获取refer属性对应url的参数
Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
//构建消费者url
//consumer://10.1.6.235/com.xiu.dubbo.service.UserService?application=dubbo- //refrence&dubbo=2.0.2&interface=com.xiu.dubbo.service.UserService&methods=queryUserInfo,sayHello&pid=17212&//side=consumer×tamp=1634111261931
URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
&& url.getParameter(Constants.REGISTER_KEY, true)) {
URL registeredConsumerUrl = getRegisteredConsumerUrl(subscribeUrl, url);
//注册 zk创建消费者节点
registry.register(registeredConsumerUrl);
//目录存放消费者url
directory.setRegisteredConsumerUrl(registeredConsumerUrl);
}
//目录服务监听zk节点下的providers、category、configurators、routers目录下的所有节点变化
//其中providers中包含dubbo服务发布者对应的url
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
//directory监听了provider 有调用的服务节点则会获取到Invoker 默认包装成FailoverClusterInvoker
//会被MockClusterWarpper包装
Invoker invoker = cluster.join(directory);
ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
return invoker;
}
查看zk节点
ls /dubbo/com.xiu.dubbo.service.UserService
RegistryDirectory 监听到了provider 则其中的服务发布后创建的节点 会通过其notify()中的refreshInvoker()中的toInvokers 获取到invoker
//toInvokers方法 中会根据provider中的dubbo:// 调用DubboProtocol的refer
invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
optimizeSerialization(url);
//创建DubboInvoker对象
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
最终获取的invoker对象为
DubboInvoker==> FilterChainInvoker(过滤器链) ==> FailoverClusterInvoker ==> MockClusterInvoker
代理工厂为JavassistProxyFactory对象 创建代理代码如下:
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}
InvokerInvocationHandler是主要代理处理逻辑类 调用逻辑在其invoke方法中
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
//Object继承的方法直接调用
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
if ("toString".equals(methodName) && parameterTypes.length == 0) {
return invoker.toString();
}
if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
return invoker.hashCode();
}
if ("equals".equals(methodName) && parameterTypes.length == 1) {
return invoker.equals(args[0]);
}
//调用 AbstractInvoker的invoker (inovke是通用方法) 不同实现应该看其中的doInvoke
//最终调用我们上面方法获取到的MockClusterInvoker对象
return invoker.invoke(new RpcInvocation(method, args)).recreate();
}
该代理对象针对stub服务会使用StubProxyFactoryWrapper进行处理 如果是stub对象则会实例化Stub对象并将创建的proxy对象通过构造函数的形式注入对象中。
#### MockClusterInvoker
public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
//获取目录服务是否有mock参数
String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
if (value.length() == 0 || value.equalsIgnoreCase("false")) {
//mock不存在直接往后调用invoker
result = this.invoker.invoke(invocation);
} else if (value.startsWith("force")) {
//强制执行mock
//force:direct mock
result = doMockInvoke(invocation, null);
} else {
//正常服务降级逻辑 先调用远程服务 如果服务调用失败则调用mock服务脚架
try {
result = this.invoker.invoke(invocation);
} catch (RpcException e) {
if (e.isBiz()) {
throw e;
} else {
if (logger.isWarnEnabled()) {
logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e);
}
result = doMockInvoke(invocation, e);
}
}
}
return result;
}
//第一个参数 invocation 调用的接口方法名和参数信息
//第二个参数 可用服务列表
//第三个参数 负载均衡策略
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
List<Invoker<T>> copyinvokers = invokers;
//非空检查
checkInvokers(copyinvokers, invocation);
//获取重试次数 默认重试次数为1
int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
if (len <= 0) {
len = 1;
}
RpcException le = null; // last exception.
List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // invoked invokers.
for (int i = 0; i < len; i++) {
//重试次数超过一个需要重新获取可用的服务列表
if (i > 0) {
checkWhetherDestroyed();
copyinvokers = list(invocation);
// check again
checkInvokers(copyinvokers, invocation);
}
//根据负载策略获取一个服务(默认负载策略为随机)
Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
//标记该invoker被调用
invoked.add(invoker);
RpcContext.getContext().setInvokers((List) invoked);
try {
//invoker服务调用
Result result = invoker.invoke(invocation);
return result;
} catch (RpcException e) {
} catch (Throwable e) {
} finally {
}
}
}
创建Protocol对象的时候会为其添加包装类ProtocolFilterWrapper 调用其refer 会调用buildInvokerChain 构造所有的配置的filter并将其转换为invoker添加到invoke链上
protected Result doInvoke(final Invocation invocation) throws Throwable {
//获取远程调用对象RpcInvocation对象
// 该对象包含我们需要调用的
// 接口,方法、参数和参数类型,以及调用对象DubboInvoker
RpcInvocation inv = (RpcInvocation) invocation;
final String methodName = RpcUtils.getMethodName(invocation);
inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
inv.setAttachment(Constants.VERSION_KEY, version);
//获取进行请求发送的客户端(这里的客户端可能与底层通信协议绑定 比如netty)
//客户端可能有多个 多个需要轮询选择一个客户端进行调用
ExchangeClient currentClient;
if (clients.length == 1) {
currentClient = clients[0];
} else {
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
//同步还是异步调用 默认同步调用 可以通过注解或者xml 设置async = true/false
//异步调用会将返回结果包装到Future中异步通知
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
//是否需要接收返回值 默认接收返回值 可以通过注解或者xml 设置return = true/false
//不接受返回值会减少dubbo请求时间
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
//调用超时时间
int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
if (isOneway) {
//无返回值 则直接由client调用send方法
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
currentClient.send(inv, isSent);
RpcContext.getContext().setFuture(null);
return new RpcResult();
} else if (isAsync) {
//异步调用 需要client调用request方法 获得返回值并将结果包装到ResponseFuture
// 放入远程调用上下文中异步通知
ResponseFuture future = currentClient.request(inv, timeout);
RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
return new RpcResult();
} else {
//默认情况 同步有返回值 调用client直接返回
RpcContext.getContext().setFuture(null);
return (Result) currentClient.request(inv, timeout).get();
}
} catch (TimeoutException e) {
//省略异常处理
}
}