Dubbo-rpc
Dubbo的官网(官网有详细讲解总体架构和代码架构):
https://dubbo.incubator.apache.org/zh/docs3-v2/java-sdk/concepts-and-architecture/
概念
Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力, 利用 Dubbo 提供的丰富服务治理特性,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。Dubbo 被设计为高度可扩展,用户可以方便的实现流量拦截、选址的各种定制逻辑。
优势
- 开箱即用
- 易用性高,如 Java 版本的面向接口代理特性能实现本地透明调用
- 功能丰富,基于原生库或轻量扩展即可实现绝大多数的微服务治理能力
- 面向超大规模微服务集群设计
- 极致性能,高性能的 RPC 通信协议设计与实现
- 横向可扩展,轻松支持百万规模集群实例的地址发现与流量治理
- 高度可扩展
- 调用过程中对流量及协议的拦截扩展,如 Filter、Router、LB 等
- 微服务治理组件扩展,如 Registry、Config Center、Metadata Center 等
- 企业级微服务治理能力
- 国内共有云厂商支持的事实标准服务框架
- 多年企业实践经验考验
Dubbo工作流程
1、Dubbo的Prodiver在启动后会去注册中心registry注册内容,注册的内容包括:IP、端口、接口列表、版本等等
2、当Consumer启动的时候,自动去Registry注册中心获取到已经注册的提供者的服务信息,并且保存服务信息的缓存
3、当Provider的信息发生变化的时候或者注册中心检测到该服务已经宕机了,则自动由注册中心异步向Consumer推送通知,并且更新Consumer中的缓存
4、Consumer同步调用Provider中的方法返回结果,基于负载均衡算法进行调用
5、每隔2分钟,Provider和Consumer自动向Monitor发送访问次数,监控器进行统计
Dubbo的整体架构设计
注册中心Registry:服务注册与发现
服务提供者Provider:暴露服务
服务消费者Consumer:调用远程服务
监控中心Monitor:统计服务的调用次数和调用时间
容器Container:服务执行容器
Dubbo的分层架构
- Service,业务层,就是咱们开发的业务逻辑层。
- Config,配置层,主要围绕 ServiceConfig 和 ReferenceConfig,初始化配置信息。
- Proxy,代理层,服务提供者还是消费者都会生成一个代理类,使得服务接口透明化,代理层做远程调用和返回结果。
- Register,注册层,封装了服务注册和发现。
- Cluster,路由和集群容错层,负责选取具体调用的节点,处理特殊的调用要求和负责远程调用失败的容错措施。
- Monitor,监控层,负责监控统计调用时间和次数。
- Portocol,远程调用层,主要是封装 RPC 调用,主要负责管理 Invoker,Invoker代表一个抽象封装了的执行体,之后再做详解。
- Exchange,信息交换层,用来封装请求响应模型,同步转异步。
- Transport,网络传输层,抽象了网络传输的统一接口,这样用户想用 Netty 就用 Netty,想用 Mina 就用 Mina。
- Serialize,序列化层,将数据序列化成二进制流,当然也做反序列化。
Dubbo2.x和Dubb3.x的区别
Dubbo2.x:在注册中心中基于接口粒度的服务发现机制
即是通过接口注册到注册中心上的,缺点就是假设接口很多的话那么注册中心注册的接口级的服务就会很多
Dubbo3.x:在注册中心中基于应用粒度的服务发现机制
即是通过应用服务注册到注册中心上的,优点是不管一个服务下的dubbo接口有多少,只要针对当前服务注册到注册中心就可以了
可以通过配置修改需要使用的dubbo注册模型
dubbo.application.register-mode=instance/interface
Dubbo3.0新特性之Triple协议
在Dubbo2.7中用的协议是dubbo协议(依赖netty),而Dubbo3.0用的就是Triple协议
优点
Triple 是 Dubbo3 提出的基于 HTTP2 的开放协议,旨在解决 Dubbo2 私有协议带来的互通性问题。相比于原有 Dubbo2 协议,Triple 有以下优势
- 原生和 gRPC 协议互通。打通 gRPC 生态,降低从 gRPC 至 Dubbo 的迁移成本(因为传输协议不同需要做转换)。
- 增强多语言生态。避免因 CPP/C#/RUST 等语言的 Dubbo SDK 能力不足导致业务难以选型适配的问题。
- 网关友好。网关无需参与序列化,方便用户从传统的 HTTP 转泛化 Dubbo 调用网关升级至开源或云厂商的 Ingress 方案。
- 完善的异步和流式支持。带来从底层协议到上层业务的性能提升,易于构建全链路异步以及严格保证消息顺序的流式服务。
如何引入Triple协议
dubbo.protocol.name=dubbo/tri
dubbo.protocol.port=20880
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-triple</artifactId>
<version>3.0.7</version>
</dependency>
SpringCloud和Dubbo的区别
- 初始定位不同:SpringCloud定位为微服务架构下的一站式解决方案;Dubbo 是 SOA 时代的产物,它的关注点主要在于服务的调用和治理
- 生态环境不同:SpringCloud依托于Spring平台,具备更加完善的生态体系;而Dubbo一开始只是做RPC远程调用,生态相对匮乏,现在逐渐丰富起来。
- 调用方式:SpringCloud是采用Http协议做远程调用,接口一般是Rest风格,比较灵活;Dubbo是采用Dubbo协议,接口一般是Java的Service接口,格式固定。但调用时采用Netty的NIO方式,性能较好。
- 组件差异比较多,例如SpringCloud注册中心一般用Eureka,而Dubbo用的是Zookeeper
SPI机制
SPI机制概念和优点
SPI又叫做Servcie product interface(服务提供接口),根据 Java 的 SPI 规范,我们可以定义一个服务接口,具体的实现由对应的实现者去提供,即服务提供者。然后在使用的时候再根据 SPI 的规范去获取对应的服务提供者的服务实现。通过 SPI 服务加载机制进行服务的注册和发现,可以有效的避免在代码中将服务提供者写死。从而可以基于接口编程,实现模块间的解耦。
API和SPI的区别
API的调用方只能依赖提供方的实现
SPI的调用方可以自定义替换API提供的默认实现(如同定制化的API一样)
Dubbo的SPI机制
SPI的概念及术语
Service:是一个公开的接口或者抽象类,定义了一个抽象的功能模块
Service provider:service接口的一个实现类
ExtensionLoader:SPI机制中的核心组件,负责在运行时通过load方法发现并加载 service provider,和jdk不一样的是,dubbo的加载器解决了并发的问题
SPI流程
大概的执行流程和JDK的一致,不同的是加载器的执行流程不同
JDK的SPI机制
SPI的概念及术语
Service:是一个公开的接口或者抽象类,定义了一个抽象的功能模块
Service provider:service接口的一个实现类
ServiceLoader:SPI机制中的核心组件,负责在运行时通过load方法发现并加载 service provider,ServiceLoader没有额外的加锁机制,存在并发问题。获取对应的实现类不够灵活,程序默认迭代获取接口的所有实现类,所以每次都要加载和实例化所有的实现类。
SPI的流程
1、服务提供方对外提供一个公共的接口
2、服务的扩展方实现结合业务可以实现该接口,然后打包成第三方的jar包
3、服务扩展方需要在根目录下创建/METF_INF/services文档命名规范则为公共接口的全路径
4、在类加载执行的时候底层会通过serviceLoader查找哪个第三方实现了该接口,并且调用该接口的实现类从而完成扩展
为什么dubbo不用jdk的spi而是要自己实现呢?
jdk SPI的缺点
1、需要遍历所有实现并实例化,浪费资源空间
2、没有使用缓存每次load都需要重新加载
Dubbo SPI的优点
1、给每个实现类配了个名字,通过名字去文件里面找到对应的实现类的全限定名然后加载实例化,按需加载
2、增加了缓存存储实,提高读取性能
3、提供了对ioc和aop等高级功能的支持,以实现更多类型的扩展
Dubbo的服务暴露
Dubbo是通过URL来作为约定的参数类型,作为配置总线
URL 具体的参数如下
- protocol:指的是 dubbo 中的各种协议,如:dubbo thrift http
- username/password:用户名/密码
- host/port:主机/端口
- path:接口的名称
- parameters:参数键值对
例如
dubbo://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=7960&qos.port=22222®istry=zookeeper×tamp=1598624821286
服务流程
服务暴露的过程起始于 Spring IOC 容器刷新完成之时,监听到对应的事件ContextRefreshedEvent,这个事件就是服务暴露的启动点,具体的流程就是根据配置得到 URL,再利用 Dubbo SPI 机制根据 URL 的参数选择对应的实现类,实现扩展。通过 javassist 代理对象封装 实现类,统一暴露出 Invoker 使得调用方便,屏蔽底层实现细节,然后封装成 exporter 存储起来,等待消费者的调用,并且根据URL将服务提供者注册到注册中心,使得消费者可以获取服务提供者的信息。
什么是invoker
Invoker是Dubbo中的实体域,也就是真实存在的。其他模型都向它靠拢或转换成它,它也就代表一个可执行体,可向它发起invoke调用。在服务提供方,Invoker用于调用服务提供类。在服务消费方,Invoker用于执行远程调用
服务暴露的源码介绍(2.2.1.RELEASE)
public List export()
DubboMetadataServiceExporter类下的export()方法
public List<URL> export() {
if (this.serviceConfig == null || !this.serviceConfig.isExported()) {
// 设置服务配置的相关参数
this.serviceConfig = new ServiceConfig();
this.serviceConfig.setInterface(DubboMetadataService.class);
this.serviceConfig.setVersion("1.0.0");
this.serviceConfig.setGroup(this.currentApplicationName);
this.serviceConfig.setRef(this.dubboMetadataService.getIfAvailable());
this.serviceConfig.setApplication(this.applicationConfig);
this.serviceConfig.setProtocol((ProtocolConfig)this.protocolConfigSupplier.get());
// 暴露方法,重点方法
this.serviceConfig.export();
if (this.logger.isInfoEnabled()) {
this.logger.info("The Dubbo service[{}] has been exported.", this.serviceConfig.toString());
}
}
// 返回暴露的url
return this.serviceConfig.getExportedUrls();
}
this.serviceConfig.export()
public synchronized void export() {
// 是否需要暴露
if (this.shouldExport()) {
// 是否是延迟暴露
if (this.shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, (long)this.getDelay(), TimeUnit.MILLISECONDS);
} else {
// 直接暴露
this.doExport();
}
}
}
this.doExport()
protected synchronized void doExport() {
// 是否已经暴露
if (this.unexported) {
throw new IllegalStateException("The service " + this.interfaceClass.getName() + " has already unexported!");
// 是否还没有暴露
} else if (!this.exported) {
// 暴露url
this.doExportUrls();
}
}
this.doExportUrls()
private void doExportUrls() {
// 通过loadRegistries方法组装【注册中心】的url
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
// 遍历当前对应的协议
Iterator var4 = this.protocols.iterator();
while(var4.hasNext()) {
ProtocolConfig protocolConfig = (ProtocolConfig)var4.next();
// 真正的进入了服务暴露的方法中
this.doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
this.doExportUrlsFor1Protocol(protocolConfig, registryURLs)
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// 判断是否有协议,若无则默认为dubbo
String name = protocolConfig.getName();
if (StringUtils.isEmpty(name)) {
name = "dubbo";
}
// 获取范围,判断是否是远程暴露还是本地暴露
scope = url.getParameter("scope");
// 如果范围什么都没有则跳出判断
if (!"none".equalsIgnoreCase(scope)) {
// 本地暴露
if (!"remote".equalsIgnoreCase(scope)) {
this.exportLocal(url);
}
// 远程暴露
if (!"local".equalsIgnoreCase(scope)) {
// 若【注册中心】的url不为空
if (CollectionUtils.isNotEmpty(registryURLs)) {
var10 = registryURLs.iterator();
while(var10.hasNext()) {
URL registryURL = (URL)var10.next();
// 若不是injvm协议
if (!"injvm".equalsIgnoreCase(url.getProtocol())) {
url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
if (monitorUrl != null) {
url = url.addParameterAndEncoded("monitor", monitorUrl.toFullString());
}
if (logger.isInfoEnabled()) {
if (url.getParameter("register", true)) {
logger.info("Register dubbo service " + this.interfaceClass.getName() + " url " + url + " to registry " + registryURL);
} else {
logger.info("Export dubbo service " + this.interfaceClass.getName() + " to url " + url);
}
}
String proxy = url.getParameter("proxy");
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter("proxy", proxy);
}
// 关键方法!!!!!!!!!
// 将实现类和接口以及url通过javassist或者jdk动态代理生成invoker对象
Invoker<?> invoker = PROXY_FACTORY.getInvoker(this.ref, this.interfaceClass, registryURL.addParameterAndEncoded("export", url.toFullString()));
// 生成包装对象
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
// 通过协议实现类将wrapperInvoker对象生成exporter对象,再将服务提供者注册到注册中心上
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
this.exporters.add(exporter);
}
}
// 若【注册中心】的url为空
} else {
if (logger.isInfoEnabled()) {
logger.info("Export dubbo service " + this.interfaceClass.getName() + " to url " + url);
}
Invoker<?> invoker = PROXY_FACTORY.getInvoker(this.ref, this.interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
this.exporters.add(exporter);
}
}
}
this.urls.add(url);
}
Dubbo的服务引入
服务引入的时机
服务的引入和服务的暴露一样,也是通过 spring 自定义标签机制解析生成对应的 Bean,Provider Service 对应解析的是 ServiceBean 而 Consumer Reference 对应的是 ReferenceBean。
public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {
...
}
服务引入的时机有两种
饿汉式
饿汉式是通过实现 Spring 的InitializingBean
接口中的 afterPropertiesSet
方法,容器通过调用 ReferenceBean
的 afterPropertiesSet
方法时引入服务。
懒汉式
懒汉式是只有当这个服务被注入到其他类中时启动引入流程,也就是说用到了才会开始服务引入。默认情况下,Dubbo 使用懒汉式引入服务,如果需要使用饿汉式,可通过配置 dubbo:reference 的 init 属性开启。
服务引用的方法
本地引入
不知道大家是否还有印象,之前服务暴露的流程每个服务都会通过搞一个本地暴露,走 injvm 协议(当然你要是 scope = remote 就没本地引用了),因为存在一个服务端既是 Provider 又是 Consumer 的情况,然后有可能自己会调用自己的服务,因此就弄了一个本地引入,这样就避免了远程网络调用的开销。所以服务引入会先去本地缓存找找看有没有本地服务
直连远程引入服务
这个其实就是平日测试的情况下用用,不需要启动注册中心,由 Consumer 直接配置写死 Provider 的地址,然后直连即可
注册中心引入远程服务
这个就是重点了,Consumer 通过注册中心得知 Provider 的相关信息,然后进行服务的引入,这里还包括多注册中心,同一个服务多个提供者的情况,如何抉择如何封装,如何进行负载均衡、容错并且让使用者无感知,这就是个技术活。
服务引用的流程
相信分析下来整个流程不难的,总结地说无非就是通过配置组成 URL ,然后通过自适应得到对于的实现类进行服务引入,如果是注册中心那么会向注册中心注册自己的信息,然后订阅注册中心相关信息,得到远程 provider
的 ip 等信息,再通过netty
客户端进行连接。并且通过directory
和 cluster
进行底层多个服务提供者的屏蔽、容错和负载均衡等,这个之后文章会详细分析,最终得到封装好的 invoker
再通过动态代理封装得到代理类,让接口调用者无感知的调用方法。
如何通过refer方法获取到对应的invoker引用对象的
源码分析
Object getObject()
public Object getObject() {
return this.get();
}
this.get()
public synchronized T get() {
if (this.destroyed) {
throw new IllegalStateException("The invoker of ReferenceConfig(" + this.url + ") has already destroyed!");
} else {
// 若得到的ref引用对象为空则初始化
if (this.ref == null) {
this.init();
}
return this.ref;
}
}
this.init()
public synchronized void init() {
if (!this.initialized) {
// 前置操作:通过配置生成map
// 生成代理对象(由invoker对象包装而成的)
this.ref = this.createProxy(map);
}
}
生成的map大致长这样
this.createProxy()
private T createProxy(Map<String, String> map) {
URL u;
// 若是本地引入
if (this.shouldJvmRefer(map)) {
// 手动创建一个url
URL url = (new URL("injvm", "127.0.0.1", 0, this.interfaceClass.getName())).addParameters(map);
// 利用SPI通过协议实现类生成对应的invoker对象
this.invoker = REF_PROTOCOL.refer(this.interfaceClass, url);
// 若是远程引入,则做一系列操作合并生成URL
} else {
this.urls.clear();
// 若配置了url,这个URL即直连的地址,或者注册中心的地址
if (this.url != null && this.url.length() > 0) {
String[] us = CommonConstants.SEMICOLON_SPLIT_PATTERN.split(this.url);
if (us != null && us.length > 0) {
for (int var17 = 0; var17 < var14; ++var17) {
// 如果是注册中心地址将map转换为查询字符串,并作为refer参数的值添加到url中
if (UrlUtils.isRegistry(url)) {
this.urls.add(url.addParameterAndEncoded("refer", StringUtils.toQueryString(map)));
// 如果是点对点会合并url,移除服务提供者的一些配置
} else {
this.urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
// 若没有配置url,走的就是注册中心引入远程服务了
} else if (!"injvm".equalsIgnoreCase(this.getProtocol())) {
this.checkRegistry();
// 加载注册中心的地址
List<URL> us = ConfigValidationUtils.loadRegistries(this, false);
if (CollectionUtils.isNotEmpty(us)) {
// 遍历地址,并且将map转换为查询字符串,并作为refer参数的值添加到url中
for(Iterator var3 = us.iterator(); var3.hasNext(); this.urls.add(u.addParameterAndEncoded("refer", StringUtils.toQueryString(map)))) {
...
}
}
}
// 若地址只有一个,则利用SPI通过协议实现类生成对应的invoker对象
if (this.urls.size() == 1) {
this.invoker = REF_PROTOCOL.refer(this.interfaceClass, (URL)this.urls.get(0));
} else {
// 有多个 URL 的时候,先遍历构建出 invoker 然后再由 StaticDirectory 封装一下,然后通过 cluster 进行合并,只暴露出一个 invoker
List<Invoker<?>> invokers = new ArrayList();
URL registryURL = null;
Iterator var16 = this.urls.iterator();
while(var16.hasNext()) {
monitorUrl = (URL)var16.next();
invokers.add(REF_PROTOCOL.refer(this.interfaceClass, monitorUrl));
if (UrlUtils.isRegistry(monitorUrl)) {
registryURL = monitorUrl;
}
}
if (registryURL != null) {
u = registryURL.addParameterIfAbsent("cluster", "zone-aware");
this.invoker = CLUSTER.join(new StaticDirectory(u, invokers));
} else {
this.invoker = CLUSTER.join(new StaticDirectory(invokers));
}
}
}
// 将invoker对象封装并生成代理对象
return PROXY_FACTORY.getProxy(this.invoker, ProtocolUtils.isGeneric(this.generic));
}
Dubbo的服务调用
调用流程
调用某个接口的方法会调用服务引用生成的代理类,然后会从集群中经过路由的过滤、负载均衡机制选择一个invoker发起远程调用,此时会记录此请求和请求的ID等待服务端的响应
服务端接收请求之后会通过参数找到之前服务暴露存储的map,得到相应的exporter,然后最终调用真正的实现类,在组装好结果返回,这个响应会带上之前请求的ID
消费者在响应之后会通过ID去找之前记录的请求,找到请求之后将响应塞到对应的Future中,唤醒等待的线程,最后消费者得到响应
什么情况下才会生成多个invoker
在多协议或者多注册中心进行服务暴露的时候才会有可能生成多个invoker对象
Dubbo的集群容错策略
1、Failover Cluster失败自动切换:dubbo的默认容错方案,当调用失败时自动切换到其他可用的节点,具体的重试次数和间隔时间可通过引用服务的时候配置,默认重试次数为1也就是只调用一次。
2、Failback Cluster失败重新恢复:在调用失败,记录日志和调用信息,然后返回空结果给consumer,并且通过定时任务每隔5秒对失败的调用进行重试
3、Failfast Cluster快速失败:只会调用一次,失败后立刻抛出异常
4、Failsafe Cluster失败安全:调用出现异常,记录日志不抛出,返回空结果
5、Forking Cluster并行调用多个服务提供者:通过线程池创建多个线程,并发调用多个provider,结果保存到阻塞队列,只要有一个provider成功返回结果,就会立刻返回结果
6、Broadcast Cluster广播模式:逐个调用每个provider,如果其中一台报错,在循环调用结束后,抛出异常
Dubbo支持的协议
1、dubbo 默认协议:
- 单一 TCP 长连接,Hessian 二进制序列化和 NIO 异步通讯
- 适合于小数据包大并发的服务调用和服务消费者数远大于服务提供者数的情况
- 不适合传送大数据包的服务
2、rmi 协议:
- 采用 JDK 标准的 java.rmi.* 实现,采用阻塞式短连接和 JDK 标准序列化方式
- 如果服务接口继承了 java.rmi.Remote 接口,可以和原生 RMI 互操作
- 因反序列化漏洞,需升级 commons-collections3 到 3.2.2版本或 commons-collections4 到 4.1 版本
- 对传输数据包不限,消费者和传输者个数相当
3、hessian 协议:
- 底层 Http 通讯,Servlet 暴露服务,Dubbo 缺省内嵌 Jetty 作为服务器实现
- 可与原生 Hessian 服务互操作
- 通讯效率高于 WebService 和 Java 自带的序列化
- 参数及返回值需实现 Serializable 接口,自定义实现 List、Map、Number、Date、Calendar 等接口
- 适用于传输数据包较大,提供者比消费者个数多,提供者压力较大
4、http 协议:
- 基于 http 表单的远程调用协议,短连接,json 序列化
- 对传输数据包不限,不支持传文件
- 适用于同时给应用程序和浏览器 JS 使用的服务
5、webservice 协议:
- 基于 Apache CXF 的 frontend-simple 和 transports-http 实现,短连接,SOAP文本序列化
- 可与原生 WebService 服务互操作
- 适用于系统集成、跨语言调用
6、thrift 协议:
- 对 thrift 原生协议的扩展添加了额外的头信息
- 使用较少,不支持传 null 值
创建Dubbo Demo(注册中心为nacos)
创建聚合项目
pom.xml(父pom)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zhouyong</groupId>
<artifactId>MyDubboRpc</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>DubboProviderOne</module>
<module>DubboProviderTwo</module>
<module>DubboConsumerOne</module>
<module>DubboRpcInterface</module>
<module>DubboProviderThree</module>
</modules>
<name>MyDubboRpc</name>
<!-- 版本控制 -->
<properties>
<junit.version>4.11</junit.version>
<nacos.version>2.2.1.RELEASE</nacos.version>
<web.version>2.2.5.RELEASE</web.version>
<lombok.version>1.18.16</lombok.version>
<httpclient.version>4.5.4</httpclient.version>
<rpc.version>1.0-SNAPSHOT</rpc.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>${nacos.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${nacos.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${web.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
<version>${nacos.version}</version>
</dependency>
<!--Dubbo Spring Cloud基于Spring Cloud Commons开发的,org.apache.http.client.HttpClient类确实存在于旧版本的httpclient中,
但是它使用内部的org.apache.http.impl.client.HttpClientBuilder类在新版本中却不存在-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>com.zhouyong</groupId>
<artifactId>DubboRpcInterface</artifactId>
<version>${rpc.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
生产者
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>MyDubboRpc</artifactId>
<groupId>com.zhouyong</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>DubboProviderOne</artifactId>
<name>DubboProviderOne</name>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<!--Dubbo Spring Cloud基于Spring Cloud Commons开发的,org.apache.http.client.HttpClient类确实存在于旧版本的httpclient中,
但是它使用内部的org.apache.http.impl.client.HttpClientBuilder类在新版本中却不存在-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.zhouyong</groupId>
<artifactId>DubboRpcInterface</artifactId>
</dependency>
</dependencies>
</project>
配置文件
spring:
application:
name: dubbo-provider-one
cloud:
nacos:
# nacos服务注册与发现
discovery:
server-addr: localhost:8848
# nacos配置中心
config:
server-addr: localhost:8848
file-extension: yml # 指定为yaml格式或者properties都行
server:
port: 8081
# dubbo 相关配置
dubbo:
# application:
# id: dubbo-provider-one
scan:
# dubbo 服务扫描基准包
base-packages: com.zhouyong.provider
protocol:
# dubbo 协议
name: dubbo
# dubbo 协议端口( -1 表示自增端口,从 20880 开始)
port: -1
registry:
# 注册中心地址
address: nacos://localhost:8848
provider:
# 负载均衡,轮询、随机random,最少活跃数leastActive
loadbalance: roundrobin
消费者
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.zhouyong</groupId>
<artifactId>MyDubboRpc</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.zhouyong</groupId>
<artifactId>DubboConsumer</artifactId>
<version>1.0-SNAPSHOT</version>
<name>DubboConsumer</name>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<!--Dubbo Spring Cloud基于Spring Cloud Commons开发的,org.apache.http.client.HttpClient类确实存在于旧版本的httpclient中,
但是它使用内部的org.apache.http.impl.client.HttpClientBuilder类在新版本中却不存在-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.zhouyong</groupId>
<artifactId>DubboRpcInterface</artifactId>
</dependency>
</dependencies>
</project>
配置文件
spring:
application:
name: dubbo-consumer-one
cloud:
nacos:
# nacos服务注册与发现
discovery:
server-addr: localhost:8848
# nacos配置中心
config:
server-addr: localhost:8848
file-extension: yml # 指定为yaml格式或者properties都行
server:
port: 8083
# dubbo 相关配置
dubbo:
# application:
# id: dubbo-consumer-one
protocol:
# dubbo 协议
name: dubbo
# dubbo 协议端口( -1 表示自增端口,从 20880 开始)
port: -1
registry:
# 注册中心地址
address: nacos://localhost:8848
cloud:
# 用于服务消费方订阅服务提供方的应用名称的列表,若需订阅多应用,使用 "," 分割。 不推荐使用默认值为 "*",它将订阅所有应用。
subscribed-services: dubbo-provider-one,dubbo-provider-two,dubbo-provider-three
consumer:
# 启动检查服务是否存在(默认true),建议关闭,不然服务不存在有可能报错
check: false
# 设置超时时间 ms
timeout: 50000
# 设置重试次数(全局),如果不想全局设置,可以在调用方按需指定
retries: 0
公共接口
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>MyDubboRpc</artifactId>
<groupId>com.zhouyong</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.zhouyong</groupId>
<artifactId>DubboRpcInterface</artifactId>
<version>1.0-SNAPSHOT</version>
<name>DubboRpcInterface</name>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
手写一个简单的dubbo rpc
gitee地址:https://gitee.com/zhouyongdage/myrpc.git
1、需要创建服务生产端和服务消费端
2、创建需要对外服务的接口以及接口的实现
3、需要将这些接口注册到注册中心中(即一个静态的map池中),这样接口可以暴露给消费端调用
4、消费端发起请求的时候需要携带调用的信息,例如调用的是哪个接口、哪个方法、哪些参数、以及参数的类型
5、需要创建一个对所有请求的拦截器
6、在消费端调用接口的时候需要对生产端的接口创建代理对象
7、对拦截到请求的参数信息进行反射调用方法并且invoke获得结果
创作不易,希望大家能够点个赞,也希望大家能帮忙指出问题,一起进步!!!谢谢大家~~