Dubbo
结构介绍
- 服务接口层(Service):与实际业务逻辑相关,根据消费者和生产者业务设计对应接口和实现;
- 配置层(Config):对外配置接口,负责Dubbo中生成各种配置类,例如协议,生产者以及具体的Bean信息的等等,方便再暴露和做负载均衡时直接使用 源码
- 服务代理层(Proxy):根据上一层生成的各个配置信息,两端根据ProxyFactory生成代理信息Invoker。
- 服务注册层(Registry):将已经生成Invoker信息导出并注册到注册中心进行服务暴露,此时生产者提供的服务可以被发现。
- 集群层(Cluster):封装提供者路由以及负载均衡,并连接注册中心以Invoker为中心扩展接口为Cluster,Directory,Router,LoadBalance;
- Invoker:是Provider的一个可调用Service的抽象,Invoker中封装了Provider地址以及Service接口信息;
- Directory表示多个Invoker,从注册中心中不断拉去,推送变更。里面包含多个Invoker,Cluster将Directory多个相同的Invoker伪装成一个Invoker对上层透明调用,伪装的过程包含了容错逻辑,调用失败重试逻辑等等;
- Router负责从多个Invoker中按路由规则选出子集;
- LoadBalance负责从多个Invoker中选择一个用于本次调用,通过选择或者自定义的负载均衡算法来选择多个相同Invoker中的某一个,失败后重选;
使用
1.zookeeper注册发现中心
2.pom引用,客户端
1.创建注册发现中心Zookeeper
## 拉去镜像启动,没有什么特殊参数配置
2.生产者
Pom引用
<!-- Aapche Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.1</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
基础配置
## dubbo提供服务基础包
dubbo.scan.base-packages=com.dubbo.provide
## dubbo远程调用时使用的协议,支持dubbo,Rmi,Hessian协议
dubbo.protocol.name=dubbo
## 当前生产者提供服务应用的端口
dubbo.protocol.port=8081
## 当前生产者提供服务应用的IP地址
#dubbo.protocol.host=192.168.101.106
## 当前生产者簇名称 用于消费者负载均衡
dubbo.provider.cluster=failover
## 注册中心,发现中心
dubbo.registry.address=zookeeper://39.108.151.72:2181
生产者
package com.dubbo.provide.service;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;
/**
* @Author: haitao.gao
* @Description:
* 随机:loadbalance="random"
*
* 轮循:loadbalance="roundrobin"
*
* 最少活跃数:loadbalance="leastactive"
*
* 一致性Hash:loadbalance="consistenthash"
*
*
* @Date: 2020/11/26 19:18
* @Version: 1.0
*/
@Service(version = "1.0.0" , executes = 12,interfaceClass = ProvideService.class,delay = 10,weight = 50,group = "test",loadbalance = "roundrobin")
@Component
public class ProvideServiceImpl implements ProvideService {
public static Integer index = 0;
@Override
public String getIndex() {
return "这是服务一";
}
}
3.消费者
package com.dubbo.provide;
import com.dubbo.provide.service.ProvideService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: haitao.gao
* @Description:
* @Date: 2020/11/26 21:03
* @Version: 1.0
*/
@RestController
@RequestMapping(value = "/v1/test")
public class CustomerController {
// 通过version来控制相同生产者不同版本的调用
// time,out来控制失败机制
// group集群模式下可以指定调用某个组下的生产者进行负载均衡
@Reference(version = "1.0.0",timeout = 10000,retries = 3,group = "test")
private ProvideService provideService;
@RequestMapping(value = "/dubbo",method = RequestMethod.GET)
public String testDubbo(){
return "\n*****************\n"+provideService.getIndex().toString()+"\n****************";
}
}
对比SpringCloud体系
组件/功能 | Dubbo | SpringCloud |
---|---|---|
注册中心 | zookeeper,multicast,redis,simple | Eureka |
服务监控 | Dubbo-monitor | Admin |
熔断器 | 不完善 | Hystrix |
服务网关 | 无 | Gateway,Zuul |
分布式设置 | 无 | Config |
服务追踪 | 无 | Sleuth |
消息总线 | 无 | Bus |
数据流 | 无 | Stream |
批量任务 | 无 | Task |
Dubbo使用PRC通信,可自定义通信协议,SpringCloud使用http或https进行通信,底层均使用TCP
注册中心
Feature | Maturity | Strength | Problem | Advise | User |
---|---|---|---|---|---|
Zookeeper注册中心 | Stable | 支持基于网络的集群方式,有广泛周边开源产品,建议使用dubbo-2.3.3以上版本(推荐使用) | 依赖于Zookeeper的稳定性 | 可用于生产环境 | |
Redis注册中心 | Stable | 支持基于客户端双写的集群方式,性能高 | 要求服务器时间同步,用于检查心跳过期脏数据 | 可用于生产环境 | |
Multicast注册中心 | Tested | 去中心化,不需要安装注册中心 | 依赖于网络拓扑和路由,跨机房有风险 | 小规模应用或开发测试环境 | |
Simple注册中心 | Tested | Dogfooding,注册中心本身也是一个标准的RPC服务 | 没有集群支持,可能单点故障 | 试用 |
支持的协议
Feature | Maturity | Strength | Problem | Advise | User |
---|---|---|---|---|---|
Simple监控中心 | Stable | 支持JFreeChart统计报表 | 没有集群支持,可能单点故障,但故障后不影响RPC运行 | 可用于生产环境 |
Feature | Maturity | Strength | Problem | Advise | User |
---|---|---|---|---|---|
Dubbo协议 | Stable | 采用NIO复用单一长连接,并使用线程池并发处理请求,减少握手和加大并发效率,性能较好(推荐使用) | 在大文件传输时,单一连接会成为瓶颈 | 可用于生产环境 | Alibaba |
Rmi协议 | Stable | 可与原生RMI互操作,基于TCP协议 | 偶尔会连接失败,需重建Stub | 可用于生产环境 | Alibaba |
Hessian协议 | Stable | 可与原生Hessian互操作,基于HTTP协议 | 需hessian.jar支持,http短连接的开销大 | 可用于生产环境 |
通信框架
Feature | Maturity | Strength | Problem | Advise | User |
---|---|---|---|---|---|
Netty Transporter | Stable | JBoss的NIO框架,性能较好(推荐使用) | 一次请求派发两种事件,需屏蔽无用事件 | 可用于生产环境 | Alibaba |
Mina Transporter | Stable | 老牌NIO框架,稳定 | 待发送消息队列派发不及时,大压力下,会出现FullGC | 可用于生产环境 | Alibaba |
Grizzly Transporter | Tested | Sun的NIO框架,应用于GlassFish服务器中 | 线程池不可扩展,Filter不能拦截下一Filter | 试用 |
序列化与反序列化方式
Feature | Maturity | Strength | Problem | Advise | User |
---|---|---|---|---|---|
Hessian Serialization | Stable | 性能较好,多语言支持(推荐使用) | Hessian的各版本兼容性不好,可能和应用使用的Hessian冲突,Dubbo内嵌了hessian3.2.1的源码 | 可用于生产环境 | Alibaba |
Dubbo Serialization | Tested | 通过不传送POJO的类元信息,在大量POJO传输时,性能较好 | 当参数对象增加字段时,需外部文件声明 | 试用 | |
Json Serialization | Tested | 纯文本,可跨语言解析,缺省采用FastJson解析 | 性能较差 | 试用 | |
Java Serialization | Stable | Java原生支持 | 性能较差 | 可用于生产环境 |
代理对象实现
Feature | Maturity | Strength | Problem | Advise | User |
---|---|---|---|---|---|
Javassist ProxyFactory | Stable | 通过字节码生成代替反射,性能比较好(推荐使用) | 依赖于javassist.jar包,占用JVM的Perm内存,Perm可能要设大一些:java -XX:PermSize=128m | 可用于生产环境 | Alibaba |
Jdk ProxyFactory | Stable | JDK原生支持 | 性能较差 | 可用于生产环境 |
容错机制
Feature | Maturity | Strength | Problem | Advise | User |
---|---|---|---|---|---|
Failover Cluster | Stable | 失败自动切换,当出现失败,重试其它服务器,通常用于读操作(推荐使用) | 重试会带来更长延迟 | 可用于生产环境 | Alibaba |
Failfast Cluster | Stable | 快速失败,只发起一次调用,失败立即报错,通常用于非幂等性的写操作 | 如果有机器正在重启,可能会出现调用失败 | 可用于生产环境 | Alibaba |
Failsafe Cluster | Stable | 失败安全,出现异常时,直接忽略,通常用于写入审计日志等操作 | 调用信息丢失 | 可用于生产环境 | Monitor |
Failback Cluster | Tested | 失败自动恢复,后台记录失败请求,定时重发,通常用于消息通知操作 | 不可靠,重启丢失 | 可用于生产环境 | Registry |
Forking Cluster | Tested | 并行调用多个服务器,只要一个成功即返回,通常用于实时性要求较高的读操作 | 需要浪费更多服务资源 | 可用于生产环境 | |
Broadcast Cluster | Tested | 广播调用所有提供者,逐个调用,任意一台报错则报错,通常用于更新提供方本地状态 | 速度慢,任意一台报错则报错 | 可用于生产环境 |
负载均衡策略
Feature | Maturity | Strength | Problem | Advise | User |
---|---|---|---|---|---|
Random LoadBalance | Stable | 随机,按权重设置随机概率(推荐使用) | 在一个截面上碰撞的概率高,重试时,可能出现瞬间压力不均 | 可用于生产环境 | Alibaba |
RoundRobin LoadBalance | Stable | 轮询,按公约后的权重设置轮询比率 | 存在慢的机器累积请求问题,极端情况可能产生雪崩 | 可用于生产环境 | |
LeastActive LoadBalance | Stable | 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差,使慢的机器收到更少请求 | 不支持权重,在容量规划时,不能通过权重把压力导向一台机器压测容量 | 可用于生产环境 | |
ConsistentHash LoadBalance | Stable | 一致性Hash,相同参数的请求总是发到同一提供者,当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动 | 压力分摊不均 | 可用于生产环境 |
路由规则
Feature | Maturity | Strength | Problem | Advise | User |
---|---|---|---|---|---|
条件路由规则 | Stable | 基于条件表达式的路由规则,功能简单易用 | 有些复杂多分支条件情况,规则很难描述 | 可用于生产环境 | Alibaba |
脚本路由规则 | Tested | 基于脚本引擎的路由规则,功能强大 | 没有运行沙箱,脚本能力过于强大,可能成为后门 | 试用 |
运行容器
Feature | Maturity | Strength | Problem | Advise | User |
---|---|---|---|---|---|
Spring Container | Stable | 自动加载META-INF/spring目录下的所有Spring配置 | 可用于生产环境 | Alibaba | |
Jetty Container | Stable | 启动一个内嵌Jetty,用于汇报状态 | 大量访问页面时,会影响服务器的线程和内存 | 可用于生产环境 | Alibaba |
Log4j Container | Stable | 自动配置log4j的配置,在多进程启动时,自动给日志文件按进程分目录 | 用户不能控制log4j的配置,不灵活 | 可用于生产环境 | Alibaba |
Dubbo角色
Provide 服务提供方
Consumer 服务消费方
Registry 服务与注册中心
- TODO
Monitor 监控中心
Container 服务运行容器
!Dubbo
Dubbo 暴露服务
Dubbo暴露服务分为延迟暴露和非延迟暴露两种:
- 延迟暴露:在初始化Spring中服务提供类Bean时,会对实现了InitializingBean的类回调其中的afterPropertySet()方法,设置延迟后服务发布就是在该方法中进行。
- 非延迟暴露:dubbo在Spring实例化完bean之后,在最后一步发布ContextRefreshEvent事件时,通知会对实现ApplicationListner的类进行回调onApplicationEvent方法,dubbo会在这个方法中进行发布服务。
在服务发布暴露的过程中,都是使用SpringConfig的export()方法进行服务的暴露,export()会将Bean对象转换为URL格式,其中Bean的属性转换为URL的参数,发布到Dubbo的注册中心中,消费者获取可提供服务列表中获取均是该类型参数可直接进行调用。
暴露过程
-
Spring 容器初始化调用
当Spring容器完成实例化Bean后,在最后一步发布ContextRefreshEvent事件时,Dubbo通过ServiceBean来监听RefreshEvent事件。通过监听事件来触发Dubbo的暴露过程export()。 -
SpringConfig的export()
- 服务提供者Bean中在初始化时@Service注解中各种变量都不会序列化所以可以直接使用;
- 检查是否延迟,如果是延迟发布则在该Bean中启动一个延迟线程池,延迟执行export();
public synchronized void export() { checkAndUpdateSubConfigs(); if (!shouldExport()) { return; } if (shouldDelay()) { delayExportExecutor.schedule(this::doExport, delay, TimeUnit.MILLISECONDS); } else { doExport(); } }
- 检查和设置注册和生成URL所需参数
public void checkAndUpdateSubConfigs() { // 在痊愈配置中使用默认配置进行定义 // 其中包括生产者所有配置,监听器,协议,等等 completeCompoundConfigs(); // 首先启动Dubbo三大中心(注册中心,元数据中心、配置中心)之一的配置中心 // 配置中心详解 https://blog.csdn.net/u012881904/article/details/95891448 // 【配置中心主要负责 外部化配置(可以理解为dubbo.properties外部化存储)、服务治理(服务治理规则的存储于通知) ConfigManager.getInstance() startConfigCenter(); // 检查服务生产者是否存在,不过该类基本没啥用,ProvideConfig基本被废弃 checkDefault(); // 检查当前运行容器信息,如果不存在则设置当前Bean的application信息 checkApplication(); // 检测注册中心,将配置文件中dubbo.registry.address 的属性值,如果是多注册中心会将注册中心都分隔开用RegistryConfig保存在(protected List<RegistryConfig> registries;)中,所以dubbo是支持多注册中心。 checkRegistry(); // 检查所配置协议,并把协议的相关配置转换为Protocols,方便后续在export时生成映射关系直接进行使用,Dubbo也支持多协议 checkProtocol(); this.refresh(); // 检查元数据中心 checkMetadataReport(); // 八寸该Provider的代理类以及实际执行类在后面暴露注册服务生成URL以及Key-Value关系时会用到 if (StringUtils.isEmpty(interfaceName)) { throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!"); } 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); } checkInterfaceAndMethods(interfaceClass, methods); checkRef(); generic = Boolean.FALSE.toString(); } if (local != null) { if ("true".equals(local)) { local = interfaceName + "Local"; } Class<?> localClass; try { localClass = ClassHelper.forNameWithThreadContextClassLoader(local); } catch (ClassNotFoundException e) { throw new IllegalStateException(e.getMessage(), e); } if (!interfaceClass.isAssignableFrom(localClass)) { throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName); } } 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); } if (!interfaceClass.isAssignableFrom(stubClass)) { throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName); } } checkStubAndLocal(interfaceClass); checkMock(interfaceClass); }
- doExport() 进行注册以及暴露服务
// 线程安全的方式进行服务暴露,确保不会重复缓存 protected synchronized void doExport() { if (unexported) { throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!"); } if (exported) { return; } exported = true; if (StringUtils.isEmpty(path)) { path = interfaceName; } doExportUrls(); }
- 根据所配置协议,逐个向每个注册中心中进行暴露服务
private void doExportUrls() { // 加载配置的所有注册中心 // 检查注册中心配置是否合法,如果合法根据registres中所配置的注册中心信息来进行对应注册中心URL的生成,方便在下面注册到远程时使用 // RUL 格式(registry://IP:PORT/org.apache.dubbo.registry.RegistryService?application=DemoProvider&dubbo=2.0.2&pid=2944&qos.enable=false®istry=zookeeper&release=2.7.1×tamp=1608811153169) List<URL> registryURLs = loadRegistries(true); // 根据配置协议进行逐个注册,实际上registres 和protocols做笛卡尔积 for (ProtocolConfig protocolConfig : protocols) { // 先拼接元数据,此时格式(test/com.dubbo.provide.service.ProvideService:1.0.0) String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version); // 将代理类和Key保存在缓存中 providedServices,当做该类已经注册 ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass); ApplicationModel.initProviderModel(pathKey, providerModel); // 实际暴露 doExportUrlsFor1Protocol(protocolConfig, registryURLs); } }
- 暴露服务
// 本地暴露服务 if (!Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) { if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { //这时候转成本地暴露的url:injvm://127.0.0.1/dubbo.common.hello.service.HelloService?anyhost=true& //application=dubbo-provider&application.version=1.0&dubbo=2.5.3&environment=product& //interface=dubbo.common.hello.service.HelloService&methods=sayHello& //organization=china&owner=cheng.xi&pid=720&side=provider×tamp=1489716708276 URL local = URL.valueOf(url.toFullString()) .setProtocol(Constants.LOCAL_PROTOCOL) .setHost(NetUtils.LOCALHOST) .setPort(0); //首先还是先获得Invoker //然后导出成Exporter,并缓存 //这里的proxyFactory实际是JavassistProxyFactory //有关详细的获得Invoke以及exporter会在下面的流程解析,在本地暴露这个流程就不再说明。 Exporter<?> exporter = protocol.export( proxyFactory.getInvoker(ref, (Class) interfaceClass, local)); exporters.add(exporter); logger.info("Export dubbo service " + interfaceClass.getName() +" to local registry"); } } // 远程暴露服务 if (!Constants.SCOPE_LOCAL.equalsIgnoreCase(scope)) { if (logger.isInfoEnabled()) { logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url); } if (CollectionUtils.isNotEmpty(registryURLs)) { for (URL registryURL : registryURLs) { url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY)); URL monitorUrl = loadMonitor(registryURL); if (monitorUrl != null) { url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString()); } if (logger.isInfoEnabled()) { logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL); } // For providers, this is used to enable custom proxy to generate invoker String proxy = url.getParameter(Constants.PROXY_KEY); if (StringUtils.isNotEmpty(proxy)) { registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy); } // 根据选择的代理工厂来将代理类和相关信息封装成Invoker类,Dubbo默认使用Javassist代理 Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); // 根据选择不同的协议封装成不同的Exporter,选择的dubbo协议不同则封装内容和逻辑会有差异,默认dubbo协议 Exporter<?> exporter = protocol.export(wrapperInvoker); exporters.add(exporter); } } else { Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url); DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this); Exporter<?> exporter = protocol.export(wrapperInvoker); exporters.add(exporter); } /** * @since 2.7.0 * ServiceData Store */ MetadataReportService metadataReportService = null; if ((metadataReportService = getMetadataReportService()) != null) { metadataReportService.publishProvider(url); } }
- 封装Invoker
@Override public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) { // 根据proxy代理类生成Invoker的包装类Wrapper,如果$开口则使用接口类型来生成Wrapper // 被final修饰后,只有在所有引用都失效后才会被GC final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type); // 直接返回初始化好的Invoker对象,并重写执行指定方法的Invoker方法,使用包装类wrapper来进行方法的调用,所以只有该Invoker被回收后,他的包装类才会被回收 return new AbstractProxyInvoker<T>(proxy, type, url) { @Override protected Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable { return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); } }; }
- 导出Invoker
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { //registry类型的Invoker,不需要做处理 if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) { return protocol.export(invoker); } //非Registry类型的Invoker,需要被监听器包装 return new ListenerExporterWrapper<T>(protocol.export(invoker), Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class) .getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY))); } public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException { //export invoker //这里就交给了具体的协议去暴露服务(先不解析,留在后面,可以先去后面看下导出过程) final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker); //registry provider //根据invoker中的url获取Registry实例 //并且连接到注册中心 //此时提供者作为消费者引用注册中心核心服务RegistryService final Registry registry = getRegistry(originInvoker); //注册到注册中心的URL final URL registedProviderUrl = getRegistedProviderUrl(originInvoker); //调用远端注册中心的register方法进行服务注册 //若有消费者订阅此服务,则推送消息让消费者引用此服务。 //注册中心缓存了所有提供者注册的服务以供消费者发现。 registry.register(registedProviderUrl); // 订阅override数据 // FIXME 提供者订阅时,会影响同一JVM即暴露服务,又引用同一服务的的场景,因为subscribed以服务名为缓存的key,导致订阅信息覆盖。 final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl); final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl); overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); //提供者向注册中心订阅所有注册服务的覆盖配置 //当注册中心有此服务的覆盖配置注册进来时,推送消息给提供者,重新暴露服务,这由管理页面完成。 // 根据调用策略来决定订阅逻辑 registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); //保证每次export都返回一个新的exporter实例 //返回暴露后的Exporter给上层ServiceConfig进行缓存,便于后期撤销暴露。 return new Exporter<T>() {} } //根据invoker的地址获取registry实例 private Registry getRegistry(final Invoker<?> originInvoker){ //获取invoker中的registryUrl URL registryUrl = originInvoker.getUrl(); if (Constants.REGISTRY_PROTOCOL.equals(registryUrl.getProtocol())) { //获取registry的值,这里获得是zookeeper,默认值是dubbo String protocol = registryUrl.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_DIRECTORY); //这里获取到的url为: //zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService? //application=dubbo-provider&application.version=1.0&dubbo=2.5.3& //environment=product&export=dubbo%3A%2F%2F192.168.1.100%3A20880%2F //dubbo.common.hello.service.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-provider%26 //application.version%3D1.0%26dubbo%3D2.5.3%26environment%3Dproduct%26 //interface%3Ddubbo.common.hello.service.HelloService%26methods%3DsayHello%26 //organization%3Dchina%26owner%3Dcheng.xi%26pid%3D9457%26side%3Dprovider%26timestamp%3D1489807681627&organization=china&owner=cheng.xi& //pid=9457×tamp=1489807680193 registryUrl = registryUrl.setProtocol(protocol).removeParameter(Constants.REGISTRY_KEY); } //根据SPI机制获取具体的Registry实例,这里获取到的是ZookeeperRegistry return registryFactory.getRegistry(registryUrl); }
Dubbo 会在 Spring 实例化完 bean 之后,在刷新容器最后一步发布 ContextRefreshEvent 事件的时候,通知实现了 ApplicationListener 的 ServiceBean 类进行回调 onApplicationEvent 事件方法,Dubbo 会在这个方法中调用 ServiceBean 父类 ServiceConfig 的 export 方法,而该方法真正实现了服务的(异步或者非异步)发布。
Netty
简介
Netty是一款基于NIO非阻塞IO模型开发的网络通信框架,相比于BIO,Netty在性能以及内存损耗方面有很大的提升。
特点
1.IO模型
在Netty中,IO操作主要分为两个部分:Acceptor(建立连接),write/read(IO读写操作)。而Netty可以通过选择不同的Reactor线程模型来选择提高IO效率:
-
单Reactor单线程模式
由于Reactor模式使用是异步非阻塞IO,所以一个线程可以独立处理所有的IO相关操作。单个线程通过Acceptor接受客户端的TCP连接请求,链路建立成功后Reactor通过Dispatch将对应ByteBuffer派发到指定的Handler上进行消息消息编解码。在模式下只有单一线程进行IO处理。 -
单Reactor多线程模式
该模式下Acceptor和write/read两种操作会由不同的线程进行担任,会有一个专门NIO线程-Acceptor线程用于监听服务端,接受客户端TCP连接消息。网络IO操作(读写操作)都会由一个NIO线程池负责,线程池使用JDK线程池实现,其中还包含一个任务队列和可用线程计数器。 -
多Reactor多线程模式
该模式下用于接受客户端创建链路连接线程不再由单一线程进行担任,而是由一个独立的NIO线程池进行处理。Acceptor线程池接收到客户端TCP连接请求后,将新创建的SocketChannel注册到IO线程池中的某个IO线程上,由他进行数据的写入,编码,解码,读出工作。
NIO组成
-
Buffer:与Channel进行交互,NIO面向的缓冲区。当NIO存在有效连接并进行性数据写入写出时都会与该Buffer进行交互。所以数据都是从Channel读入缓冲区,从缓冲区写入Channel中。
-
Channel:信道,缓冲区和IO源的连接,不同于Stream的单向传输,Channel是可以进行双向传输的。在Channel进行数据移动的时候会调用native方法,将按照数据块为单位写入/写出。相比于Stream的IO操作,Channel在效率方面会高出三分之一左右(原因还未知,但主要两方面,传输方式零拷贝,传输数据单位)。Channel VS Stream
-
Selector:多路复用器。可使用一个单独的线程管理多个Channel,串行遍历进行IO操作。其中open方用户创建Selector,register用于向Selector注册Channel。Java中的Selector默认使用Epoll但是可以通过参数来指定其他的多路复用器。
-
NIOEventLoopGroup:时间循环处理类,内部维护一个由EventExector children []默认大小是处理器核数*2构成的线程池。
2.内存零拷贝
零拷贝实现
1.相比于BIO面向流开发,Netty使用NIO模型面向(堆外内存)缓冲区进行开发。Netty接收和发送ByteBuffer采用DIRECT BUFFERS(堆外缓存),使用堆外内存进行Socket读取不用进行字节缓冲区的二次拷贝。传统堆内存进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中再写入Socket中,相比堆外内存,传统堆内存会多一次缓冲区内存拷贝。
2.Netty提供组合Buffer对象,例如CompositeByteBuf可以将报文进行粘包/切包不再像传统内存拷贝方式将几个小的Buffer合并成一个Buffer进行处理。
3.Netty文件传输采用transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。
3.串形化读写处理
Netty在IO线程内部进行串行化操作,避免了多线程竞争导致性能下降。在IO线程内部通会通过线程池启动多个串行化线程并行运行,每个线程通过串行方式遍历所有与该线程绑定的Channel,在NioEventLoop(IO线程池)读到消息后会调用ChannelPipeline的fireChannelRead进行数据的读或者写,只要线程池不主动切换线程会由NioEventLoop调用用户的Handler进行操作,期间不进行线程切换所以不会导致资源的竞争避免锁操作。
4.高性能序列化协议
Netty默认提供对Google Protobuf 的支持,可以通过扩展Netty的编解码接口,可以让用户实现其他高性能序列化框架,例如Thrift的压缩二进制编解码框架。
5.内存池设计
内存池原理
为了避免频繁的内存分配给系统带来负担以及GC对系统性能带来的波动,Netty使全新的内存池来管理内存的分配和回收,内存池的设计主要参考Slab(Memcached的内存池),Buddy分配两种思路来进行内存管理。
Slab分配:主要思路是将内存分割成大小不等的内存块,用户线程请求内存时根据请求的内存代销分配最贴近的Size的内存块。在减少碎片的同时又能很好的避免内存浪费。
Buddy:在分配内存的过程中把一些内存块灯亮分割,回收时合并,尽可能保证系统中有较大的连续可分配内存。