1、RPC框架具备的功能点
RPC是协议
既然是协议就只是一套规范,那么就需要有人遵循这套规范来进行实现。目前典型的RPC实现包括:Dubbo、Thrift、GRPC、Hetty等。
网络协议和网络IO透明
既然RPC的客户端认为自己是在调用本地对象。那么传输层使用的是TCP/UDP还是HTTP协议,者是一些其他的网络协议它就不需要关心了。既然网络协议对其透明,那么调用过程中,使用的是哪一种网络IO模型调用者也不需要关心。
信息格式对其透明
在本地应用程序中,对象调用需要传递一些参数,会返回一个调用结果。对象内部是如何使用这些参数,并计算出处理结果的,调用方是不需要关心的。那么对于RPC来说,这些参数会以某种信息格式传递给网络上的另外一台计算机,这个信息格式是怎样构成的,调用方是不需要关心的。
有跨语言能力
调用方实际上也不清楚远程服务器的应用程序是使用什么语言运行的。那么对于调用方来说,无论服务器方使用的是什么语言,本次调用都应该成功,并且返回值也应该按照调用方程序语言所能理解的形式进行描述。
2、整合示例
- xml配置
- 自动装配注解
- api编码
3、xml配置分析
### 3.1、xml配置
我们在配置使用了<dubbo :标签>
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="dubbo-core" />
<dubbo:protocol id="dubbo" name="dubbo" />
<!-- 使用zookeeper注册中心暴露服务地址,多个地址中间用,号隔开 ?backup=192.168.137.131:2181,192.168.137.132:2181 -->
<dubbo:registry address="zookeeper://182.92.189.235:2181" />
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20881" />
<!-- 声明需要暴露的服务接口 -->
<!--<dubbo:service timeout="3000" interface="com.xiu.dubbo.service.DemoService" ref="demoService"-->
<!--cluster="failover" />-->
<!-- 也可以使用注解的形式进行处理 使用注解的方式需要对服务提供者使用
@Service(dubbo自己的注解而非Spring) 服务使用者使用@Reference注解修饰 -->
<dubbo:annotation package="com.xiu.dubbo.service" />
涉及到自定义标签配置:spring标签解析逻辑、spring自定义标签解析
3.2、dubbo自定义标签处理
标签 | 描述 |
---|---|
<dubbo:application /> | 用于配置应用信息,dubbo计算其依赖关系 |
<dubbo: module /> | 用于配置应用模块信息(适用于一个应用多模块) |
<dubbo:protocol /> | 用于配置服务提供者的访问协议 |
<dubbo:registry /> | 用于配置注册中心 Multicast、Zookeeper、Redis、Dubbo四种注册中心 |
<dubbo:provider /> | 用于配置服务提供者的通用配置值,provider是原始的服务提供方式:配置参数超级多,比较繁琐,其上的配置为默认可以被所有的<dubbo:service />直接继承,即设置dubbo:service和dubbo:protocol标签的默认值 |
<dubbo:service /> | 用于配置服务提供者暴露自己的服务 |
<dubbo:consumer /> | 用于配置服务消费者的默认值,即dubbo:reference标签的默认值,同 <dubbo:provider />与<dubbo:service />关系 |
<dubbo:reference /> | 用于配置服务消费者引用服务 |
<dubbo:annotation /> | 支持注解扫描的方式注册service服务和引用服务 |
<dubbo: monitor /> | 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的创建
3.3、dubbo标签解析
类 | 说明 |
---|---|
DubboBeanDefinitionParser | 用于解析application、module、server、reference等 |
AnnotationBeanDefinitionParser | 用于解析annotation标签 注解扫描 |
- DubboBeanDefinitionParser :将除了<dubbo:annotion />标签的其他dubbo标签转换为对应的AbstractConfig 的BeanDefinition对象后续会被spring实例化
class对象 | 配置属性信息 |
---|---|
ApplicationConfig =====> <dubbo:application /> | id、name |
ProtocolConfig =====> <dubbo:protocol /> | id、name、port |
RegistryConfig =====> <dubbo:registry /> | id、address |
ServiceBean =====> <dubbo:service /> | id、name、timeout、interface、ref |
ReferenceBean =====> <dubbo:reference /> | id、name、interface |
- AnnotationBeanDefinitionParser: 注解扫描相关: 通过方法向spring容器中注册了两个bean后置处理器
- ServiceAnnotationBeanPostProcessor 用于扫描包下所有使用@Service的类转换为ServiceBean的BeanDefinition
- ReferenceAnnotationBeanPostProcessor 用于扫描包下所有使用@Reference的类转换为ReferenceBean的BeanDefinition
3.4、服务提供者和消费者实例化
ServiceBean和ReferenceBean两者都实现了InitializingBean接口的afterPropertiesSet方法 该方法会在对应的bean实例化完后的初始化被调用
- ServiceBean:为该bean实例添加应用applicationConfig、模块MoudleConfig、协议ProtocalConfig、注册RegistryConfig、监控Monitor、provider(全局)属性, 最后进行服务发布export()
- ReferenceBean:为该bean实例添加应用applicationConfig、模块MoudleConfig、协议ProtocalConfig、注册RegistryConfig、监控Monitor、consumer(全局)属性,最后进行远程服务调用getObject()
4、springBoot+dubbo整合
需要在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(用于后面实例化)
5、api编码
5.1、服务发布
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){
}
}
5.2、服务调用
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<RegistryConfig> 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);
}
6、dubbo模块概览
-
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、服务发布原理
### 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();
}
}
延迟部署应用场景:
-
缓存预热:当我们的暴露服务需要依赖一些静态数据,这些静态数据是通过加载数据库或者文件然后缓存到应用内存中。此时我们可以通过在服务延迟暴露的时间段内进行缓存预加载。
-
依赖资源:假设我们对外提供的服务需要依赖另外一个服务,而另外一个服务的暴露时间比较缓慢,那么我们就可以把当前对外暴露的服务进行延迟暴露,这样就可以减少当调用依赖服务时出现超时异常的情况。
7.2、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<