Dubbo知识体系梳理
2020-09-04 10:21:07 0 举报
AI智能生成
Dubbo知识体系梳理
作者其他创作
大纲/内容
服务引用
dubbo服务引用是通过spring的schema实现的。消费端的配置如下
<dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService"/>
进入DubboNamespaceHandler,ReferenceBean 实现了 spring 的 FactoryBean 接口 , 该接口有一个 T getObject() 函数 ,dubbo 服务引用的开端就是在实现的这个 getObject() 函数中。当对任意服务 Interface 进行自动注入或者 getBean 获取时,就会触发服务的引用过程
分支主题
连接注册中心,获取服务提供者列表,创建invoker
进入ReferenceConfig的createProxy,负责创建Invoker实例和创建代理对象
RegistryProtocol.refer方法。它主要是获取注册中心,并执行服务引用
public class RegistryProtocol implements Protocol {
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
//转换协议
url = url.setProtocol(url.getParameter("registry", "dubbo")).removeParameter("registry");
//通过zookeeper连接到注册中心
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("refer"));
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);
}
}
return doRefer(cluster, registry, type, url);
}
}
doRefer(cluster, registry, type, url)
向注册中心注册服务消费者(本身)
向注册中心订阅服务提供者
directory.subscribe
ZookeeperRegistry.doSubscribe
通过注册中心获取到服务提供者的url,调用notify方法。同时还订阅了服务提供者的节点数据变化,如果有变化也调用notify方法。
而在notify方法中,最终会根据url调用服务引用方法refer,再封装成InvokerDelegate对象。
invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl)
通过Cluster创建Invoker对象
创建通信客户端
NettyClient
DubboInvoker,它通过一个构造方法返回Invoker对象
订阅服务提供者节点,数据变更时获得通知
连接通信服务器,获得客户端
创建代理并返回
此时,dubbo:reference声明的对象就是这个代理对象
消费者在引用服务的过程中如果和提供者不在同一 JVM 实例内,需要去打开网络连接,来和服务提供者进行网络通信。打开网络连接的过程和服务端在网络中暴露服务的顺序一致 Transporter , Exchanger , ExchangeClient 可选的实现有 NettyClient (默认) , MinaClient , GrizzlyClient 。消费者建立客户端打开网络连接之后要和所订阅的服务端建立连接
分支主题
容错机制
Failover Cluster
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟,可通过retries="2"来设置重试次数(不含第一次)
Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写 操作,比如新增记录
Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作
Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操 作
Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最 大并行数
Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错(2.1.0开始支持) 。通常用于通 知所有提供者更新缓存或日志等本地资源信息
降级
分类
是否自动化
自动开关
超时降级:配置好超时时间和超时重试次数和机制,并使用异步机制探测回复情况
失败次数降级:主要是一些不稳定的api,当失败调用次数达到一定阀值自动降级,同样要使用异步机制探测回复情况
故障降级:比如要调用的远程服务挂掉了(网络故障、DNS故障、http服务返回错误的状态码、rpc服务抛出异常),则可以直接降级。降级后的处理方案有:默认值、兜底数据、缓存
限流降级:当达到限流阀值,后续请求会被降级,比如抢购降级后的处理方案可以是:排队页面、无货、错误页
人工开关
功能
读服务降级:比如一些直接从DB读取的数据,改为从缓存中获取
写服务降级:比如在秒杀抢购业务中,由于并发的数量比较大。除了在各个层面上限流、使用队列等方式应对,还可以对写库进行降级,可以将库存扣减操作在内存中进行,当高峰过去之后,再异步的同步至DB中做扣减操作
访问链路
页面降级:比如在大促时,某些页面占用了稀缺资源,可以对整个页面进行降级。或者当页面上会异步加载推荐信息等一些非核心的业务时,此时如果响应变慢,则可以进行降级处理
其他类型:比如在系统繁忙时,可以将爬虫的流量直接丢弃,当高峰过后,再自动恢复。秒杀业务中,风控系统可以识别刷子/机器人,然后可以直接对这些用户执行拒绝服务
策略
1、一定是先降级优先级低的接口,两权相害取其轻,区分核心和非核心服务
2、如果服务链路整体没有性能特别差的点,比如就是外部流量突然激增,那么就从外到内开始降级
3、如果某个服务能检测到自身负载上升,那么可以从这个服务自身做降级
4、业务支持降级策略,放通策略
dubbo服务降级策略
force:return策略:表示服务调用方在调用该接口服务时候会直接在客户端内返回设置的mock值,而不会在通过远程调用方式调用服务提供者。比如使用mock=force:return+null表示消费方对该服务的方法调用都直接返回null值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响
fail:return策略:表示服务调用方调用服务提供方服务失败后再返回设置的mock值,并不会抛出异常。需要注意的是并不是调用一次失败后就直接返回mock值,具体和设置的集群容错方式(retries)有关。比如使用mock=fail:return+null表示消费方对该服务的方法调用在失败后(在抛出RpcException异常时执行),不抛异常直接返回null值。用来容忍不重要服务不稳定时对调用方的影响
Hystrix
Hystrix使用命令模式HystrixCommand(Command)包装依赖调用逻辑,每个命令在单独线程中/信号授权下执行。可配置依赖调用超时时间,当调用超时时直接返回或执行fallback逻辑。为每个依赖提供一个小的线程池(或信号),如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败判定时间。请求失败(异常,拒绝,超时,短路)时执行fallback(降级)逻辑。提供熔断器组件,可以自动运行或手动调用,停止当前依赖一段时间(10秒),熔断器默认错误率阈值为50%,超过将自动运行。提供近实时依赖的统计和监控
在Hystrix的整个机制中,涉及到依赖边界的地方,都是通过这个Command模式进行调用的,显然这个Command负责了核心的服务熔断和降级的处理,子类要实现的方法主要有两个
1、run方法:实现依赖的逻辑,或者说是实现服务之间的调用
2、getFallBack方法:实现服务降级处理逻辑,只做熔断处理的则可不实现
Sentinel
分支主题
1、授权:通过配置白名单和黑名单的方式分布式系统的接口和方法进行调用权限的控制
2、限流:对特定资源进行调用的保护,防止资源的过度使用
3、降级:判断依赖的资源的响应情况,但依赖的资源响应时间过长时进行自动降级,并且在指定的时间后自动恢复调用
4、监控:提供了全面的运行状态监控,实时监控资源的调用情况,如QPS、响应时间、限流降级等信息
粒度
服务接口:resourceName为接口全限定名,如:
com.alibaba.csp.sentinel.demo.dubbo.FooService
服务方法:resourceName为接口全限定名:方法签名,如:
com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String)
选举算法
zk的leader选举原理
选举算法
基于UDP的LeaderElection
基于UDP的FastLeaderElection
基于UDP和认证的FastLeaderElection
基于TCP的FastLeaderElection
服务器状态
LOOKING:不确定Leader状态,该状态下认为集群中没有Leader,会发起Leader选举
FOLLOWING:跟随者状态,表明当前服务器角色是Follower,并且知道Leader是谁
LEADING:领导者状态,代表当前服务器角色是Leader
OBSERVING:观察者状态:表明当前服务器角色是Observer,不参与Leader的选举,也不参与集群写操作时的投票
选举
自增选举轮次
Zookeeper规定所有有效的投票都必须在同一轮次中。每个服务器在开始新一轮投票时,会先对自己维护的logicClock进行自增操作
初始化投票
每个服务器在广播自己的选票前,会将自己的投票箱清空
发送初始化投票
每个服务器最开始都是通过广播把票投给自己
接收外部投票
服务器会尝试从其它服务器获取投票,并记入自己的投票箱内。如果无法获取任何外部投票,则会确认自己是否与集群中其它服务器保持着有效连接。如果是,则再次发送自己的投票;如果否,则马上与之建立连接
注册中心
Zookeeper 注册中心
基于分布式协调系统 Zookeeper 实现,采用 Zookeeper 的 watch 机制实现数据变更
redis 注册中心
基于 redis 实现,采用 key/Map 存储,住 key 存储服务名 和类型,Map 中 key 存储服务 URL,value 服务过期时间。基于 redis 的发 布/订阅模式通知数据变更
Multicast 注册中心
Multicast 注册中心不需要任何中心节点,只要广播地 址,就能进行服务注册和发现。基于网络中组播传输实现
Simple 注册中心
协议
Dubbo
默认NIO,单一长连接
因为服务的现状大都是服务提供者少,通常只有几台机器,而服务的消费者多,可能整个网站都在访问该服务
通过单一连接,保证单一消费者不会压死提供者,
长连接,减少连接握手验证等,
并使用异步IO,复用线程池,防止C10K问题
二进制序列化,小数据量,100K
数据量中等,不适合文件传输
RMI
RMI协议采用JDK标准的java.rmi.*实现,采用阻塞式短连接和JDK标准序列化方式
hessian
Hessian协议用于集成Hessian的服务,Hessian底层采用Http通讯,采用Servlet暴露服务,Dubbo缺省内嵌Jetty作为服务器实现
http
采用Spring的HttpInvoker实现
webservice
基于CXF的frontend-simple和transports-http实现
thrif
Thrift是Facebook捐给Apache的一个RPC框架,当前 dubbo 支持的 thrift 协议是对 thrift 原生协议的扩展,在原生协议的基础上添加了一些额外的头信息,比如service name,magic number等
负载均衡策略
Random LoadBalance
随机选取提供者策略,有利于动态调整提供者权 重。截面碰撞率高,调用次数越多,分布越均匀
RoundRobin LoadBalance
轮询选取提供者策略,平均分布,但是存在请求累积的问题
LeastActive LoadBalance
最少活跃调用策略,相同活跃数的随机,活跃数指调用前后计数差,使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大
ConstantHash LoadBalance
一致性 Hash 策略,使相同参数请求总是发到同一提供者,一台机器宕机,可以基于虚拟节点,分摊至其他提供者,避免引起提供者的剧烈变动
缺省只对第一个参数Hash,如果要修改,请配置<dubbo:parameter key="hash.arguments" value="0,1" />
缺省用160份虚拟节点,如果要修改,请配置<dubbo:parameter key="hash.nodes" value="320" />
常见问题
服务注册与发现的流程
分支主题
Provider(提供者)绑定指定端口并启动服务
提供者连接注册中心,并发本机 IP、端口、应用信息和提供服务信息 发送至注册中心存储
Consumer(消费者),连接注册中心 ,并发送应用信息、所求服务信息至注册中心
注册中心根据 消费 者所求服务信息匹配对应的提供者列表发送至 Consumer 应用缓存。
Consumer 在发起远程调用时基于缓存的消费者列表,由LoadBalance组件执行负载均衡,从中挑选一个invoker进行调用
Provider 状态变更会实时通知注册中心、在由注册中心实时推送至 Consumer
consumer和provider都异步通知监控中心
设计的原因
Consumer 与 Provider 解偶,双方都可以横向增减节点数。
注册中心对本身可做对等集群,可动态增减节点,并且任意一台宕掉 后,将自动切换到另一台
去中心化,双方不直接依懒注册中心,即使注册中心全部宕机短时间 内也不会影响服务的调用
服务提供者无状态,任意一台宕掉后,不影响使用
哪些是长连接,哪些是短连接
服务端、消费端到注册中心,消费端到服务端是长连接
服务端,消费端到monitor是短连接
注册中心挂了怎么办
可以继续通信,因为刚开始初始化的时候,消费者会将提供者的地址等信息拉取到本地缓存,所以注册中心挂了可以继续通信
扩展
注册中心对等集群,任意一台宕掉后,会自动切换到另一台
注册中心全部宕掉,服务提供者和消费者仍可以通过本地缓存通讯
服务提供者无状态,任一台宕机后,不影响使用
服务提供者全部宕机,服务消费者会无法使用,并无限次重连等待服务者恢复
注册中心可以不用zk吗
可以,还有redis,muticast,simple
服务在zk中的节点是怎么存储的
分支主题
分支主题
所有 Dubbo 相关的数据都组织在 /dubbo 的根节点下
二级目录是服务名,如 com.foo.BarService
三级目录有两个子节点,分别是 providers 和 consumers,表示该服务的提供者和消费者
四级目录记录了与该服务相关的每一个应用实例的 URL 信息,在 providers 下的表示该服务的所有提供者,而在 consumers 下的表示该服务的所有消费者。举例说明,com.foo.BarService 的服务提供者在启动时将自己的 URL 信息注册到 /dubbo/com.foo.BarService/providers 下;同样的,服务消费者将自己的信息注册到相应的 consumers 下,同时,服务消费者会订阅其所对应的 providers 节点,以便能够感知到服务提供方地址列表的变化
核心功能
Remoting:网络通信框架,提供对多种 NIO 框架抽象封装,包括 “同步转异步”和“请求-响应”模式的信息交换方式
Cluster:服务框架,提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持
Registry:服务注册,基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器
架构
分支主题
服务接口层(Service):该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现
配置层(Config):对外配置接口,以ServiceConfig和ReferenceConfig为中心,可以直接new配置类,也可以通过spring解析配置生成配置类。
服务代理层(Proxy):服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为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。Protocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理。Invoker是实体域,它是Dubbo的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现
信息交换层(Exchange):封装请求响应模式,同步转异步,以Request和Response为中心,扩展接口为Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer
网络传输层(Transport):抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel、Transporter、Client、Server和Codec
数据序列化层(Serialize):可复用的一些工具,扩展接口为Serialization、 ObjectInput、ObjectOutput和ThreadPool
SPI
java spi的具体约定为:当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader
Dubbo的SPI主要改进了JDK标准的SPI实现
JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源
增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点
核心概念
扩展点 Dubbo作用灵活的框架,并不会强制所有用户都一定使用Dubbo提供的某些架构。例如注册中心(Registry),Dubbo提供了zk和redis,但是如果我们更倾向于其他的注册中心的话,我们可以替换掉Dubbo提供的注册中心。针对这种可被替换的技术实现点我们称之为扩展点,类似的扩展点有很多,例如Protocol,Filter,Loadbalance等等
Wrapper Dubbo在加载某个接口的扩展类时候,如果某个实现中有一个拷贝类构造函数,那么该接口实现就是该接口的包装类,此时Dubbo会在真正的实现类上层包装上盖Wrapper。即这个时候从ExtensionLoader中返回的实际扩展类是被Wrapper包装的接口实现类
Adaptive 这个自适应的扩展点比较难理解,所以这里直接以一个例子来讲解:在RegistryProtocol中有一个属性为Cluster,其中Protocol和Cluster都是Dubbo提供的扩展点,所以这时候当我们真正在操作中使用cluster的时候究竟使用的哪一个cluster的实现类呢?是FailbackCluster还是FailoverCluster?Dubbo在加载一个扩展点的时候如果发现其成员变量也是一个扩展点并且有相关的set方法,就会在这时候将该扩展点设置为一个自适应的扩展点,自适应扩展点(Adaptive)会在真正使用的时候从URL中获取相关参数,来调用真正的扩展点实现类。
Activate 官网的叫法是自激活,其实这个更合适的叫法我认为是条件激活,所以Activate的作用主要是:提供一种选择性激活的条件,可以是我们通过相关的配置来确定激活哪些功能
ExtensionLoader
为了获得一个扩展点的适配类,首先会看缓存中有没有已经加载过的适配类,如果有的话就直接返回,没有的话就进入第2步
加载所有的配置文件,将所有的配置类都load进内存并且在ExtensionLoader内部做好缓存,如果配置的文件中有适配类就缓存起来,如果没有适配类就自行通过代码自行创建适配类并且缓存起来
在加载配置文件的时候,会依次将包装类,自激活的类都进行缓存
在加载配置文件的时候,会依次将包装类,自激活的类都进行缓存
调用过程
消费端触发请求。消费端读取到所有符合条件的服务提供者invoker之后,由LoadBalance组件执行负载均衡,从中挑选一个invoker进行调用
消费端请求编码。主要是封装消息,序列化对象,即把对象转换成流(默认使用hessian2进行序列化操作)。将数据写入到类型为ChannelBuffer的buffer参数中。然后把封装好的ChannelBuffer写到链路发送到服务端
提供端请求解码。客户端发送数据到服务端时会触发服务端的上行IO事件并且启动处理器回调,由NettyCodecAdapter.decoder对请求进行解码,把消息翻译成提供端可理解的消息。主要操作是读取channelbuffer对象,并进行半包处理。读取到完成的数据后,进行消息解析
提供端处理请求。调用invoker.invoke时,通过反射调用最终的服务实现执行相关逻辑。服务执行结束之后,创建一个Response对象返回给客户端。在执行服务实现时会出现两种结果:成功和失败,如果成功,把返回值设置到Response的result中,Response的status设置成OK,如果失败,把失败异常设置到Response的errorMessage中,status设置成SERVICE_ERROR
提供端响应结果编码。提供端要按照和消费端的协议把Response按照特定的协议进行编码,把编码后的数据写回到消费端。主要就是把服务的数据进行封装,封装消息头,消息体,最后数据会写入到通信链路中
消费端响应结果解码。服务端给客户端回写数据之后,客户端会收到IO事件,此时消费端会唤醒接口调用线程(因为消费端调用接口的时候请求写到提供端之后,会调用DefaultFuture.get阻塞等待响应结果)。调用端即拿到了接口的调用结果(返回值或异常),整个远程服务接口的调用流程就完成了
服务暴露流程
Duboo服务暴露顺序简图
容器初始化
spring通过在DubboNamespaceHandler注册dubbo解析器DubboBeanDefinitionParser,在加载Bean的时候同时解析dubbo标签并加载dubbo标签解析后的ServiceBean
如果设置了延时暴露
ServiceBean 实现了 InitializingBean接口
类初始化完成后调用afterPropertiesSet方法
afterPropertiesSet
get Provider
set Provider
各种信息封装到ServiceBean中
如果不设置延时暴露
实现了ApplicationListener接口
在Spring容器初始化完成后会调用onApplicationEvent方法,会对容器发生的事件进行处理 , 当有事件发布时 spring 调用每一个 Listener 的 onApplicationEvent 方法
public void onApplicationEvent(ApplicationEvent event) {
if (ContextRefreshedEvent.class.getName().equals(event.getClass().getName())) {
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
// dubbo 服务暴露
export();
}
}
}
暴露
Invoker
入口
ServiceConfig#doExport()
配置进行检查与更新,例如provider是否为空,注册中心是否为空,protocols是否为空。然后检测是否应该暴露,如果不应该暴露,则直接结束;然后检测是否配置了延迟加载,如果是,则使用定时器来实现延迟加载的目的
用expoer方法里面的doExport()方法。通过exported变量判断是否暴露,如果为true,直接返回;否则先设置为true,然后执行doExportUrls()方法
调用doExportUrls()方法,先通过loadRegistries加载注册中心链接,然后遍历ProtocolConfig集合,它是用户指定的协议集合(比如Dubbo、REST),在里面执行doExportUrlsFor1Protocol(protocolConfig, registryURLs)方法
执行doExportUrlsFor1Protocol(protocolConfig, registryURLs)方法,刚开始是组装URL
它把metrices、application、module、provider、protocol等所有配置都放入到map中
通过反射获取interfaceClass的方法列表,先做签名校验,判断该服务是否有配置的方法存在,然后该方法签名是否有这个参数存在,都核对成功才将method的配置加入到map中
将范化调用、版本号、method或者methods、token等信息加入到map
获取服务暴露地址和端口号,利用map内数据组装成URL
根据url中的scope参数决定服务导出方式。scope=none,不导出服务;scope=local,导出到本地;scope=remote,导出到远程
执行proxyFactory.getInvoker(ref, (Class)interfaceClass, url)方法,来获取invoker,Dubbo默认的ProxyFactory实现类是JavasistProxyFacoty方法
Exporter
本地暴露
调用exportLocal(url)。首先判断URL的协议头如果等于injvm,说明已经导出到本地了,无需再次导出。如果不是,则创建一个新的URL并将协议头设置为injvm,并另外设置主机名和端口。然后创建invoker,并调用InjvmProtocal的export()方法暴露服务
njvmProtocol的export方法仅创建一个InjvmExporter,无其他逻辑
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}
InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {
super(invoker);
this.key = key;
this.exporterMap = exporterMap;
exporterMap.put(key, this);
}
注册中心暴露
总体流程
调用doLocalExport()方法暴露服务
向注册中心注册服务
向注册中心进行订阅ovrride数据
创建并返回DestroyableExporter, 调用doLocalExport()方法暴露服务
首先根据invoker得到key,从bounds缓存变量中尝试获取exporter,如果获取不到,则调用Protocal的export方法,默认的协议是dubbo,所以调用的DubboProtocol的export方法。
调用DubboProtocol的export方法,首先从invoker中获取对用的URL,然后根据服务组名、服务名、服务版本号和端口组成key,然后创建一个DubboExporter
调用openServer(url)方法。首先根据url获取host:port,用于标识当前的服务器实例。在同一台机器上,同一个端口仅允许启动一个服务器实例。若某个端口上已有服务器实例,此时调用reset方法重置一些服务器的配置;如果没有则调用createServer(url)方法创建一个服务器实例
调用createServer(url)方法,有三个核心的逻辑,首先检测是否存在server参数所代表的Transporter拓展,即网络传输方式(如Netty, Mina),如果没有,则抛出异常;然后通过Exchangers.bind(url, requestHandler)方法创建服务器实例;最后是检测是否支持client参数所表示的Transporter拓展,不存在也是抛出异常
Exchange层是为了封装请求/响应模式,例如:把同步请求转化为异步请求。默认的扩展点实现类是HeaderExchanger
调用HeaderExchanger的bind方法
根据自适应扩展机制动态创建一个Transporter,Dubbo默认是NettyTransporter
调用NettyTransporter.bind(URL, ChannelHandler)方法。创建一个NettyServer实例
调用NettyServer.doOPen()方法,服务器被开启,服务也被暴露出来了
服务本地暴露
dubbo 在进行服务的本地暴露时会将 host , port , protocol (dubbo 中的 protocol) 进行修改,host 会改为 127.0.0.1 , port 改为 0 , protocol 改为 injvm
通过 ProxyFactory 获取到一个 Invoker 实例 , Invoker 中封装了服务接口实现类的实例和服务接口类型的 Class 实例,Invoker 中会去调用服务中的函数
ProxyFactory 扩展点默认使用的是 JavassistProxyFactory 。Invoker 调用服务API(服务接口中的函数)的时候都会去反射的进行调用
JavassistProxyFactory 在创建 Invoker 之前会先获取一个 Wrapper 对象 , Wrapper 的作用是动态的生成代码,动态的生成 Class 实例,获取一个 Wrappr 对象的实例 , 使得服务接口中的方法都能用对象进行直接调用,不通过反射的方式,并且会被已经生成的 Wrapper 实例进行缓存
Wrapper 的 invokeMethod 是动态生成的 , 在调用 invokeMethod 方法时 , invokeMethod 方法内部是用对象直接去调用方法,不用进行反射。 动态生成的 invokeMethod 函数代码
有了 Invoker 之后就该获取一个 Exporter
ExtensionLoader 在获取一个扩展点的扩展时,会检查该扩展点类型有没有包装类型的扩展,如果有的话会用包装类型的扩展对获取到的源扩展进行层层包装 (参考 ExtensionLoader 的 createExtension 函数)。InJvmProtocol 被包装了两层。ProtocolListenerWrapper 对 Exporter 进行了包装,目的是获取到一组 ExporterListener 对服务的暴露做出响应 , 使用观察者模式当有服务暴露时就可以针对服务暴露这件事进行一些操作。ProtocolFilterWrapper 没有对 Exporter 进行任何包装 , 它对 Invoker 做了处理 , 用一组 Filter 将 Invoker 层层包装 , 在 Invoker 被调用时 , 要经过 Filter 的链条最终调用到 Invoker
服务网络暴露
dubbo 的网络暴漏也是通过 Invoker 到 Exporter 的过程 , 在这个过程中要加入网络通信的处理
分支主题
服务的网络暴漏过程是在由 Invoker 到 Exporter 这个过程中完成的
Transporter , ChannelHandler 是用来处理底层的网络连接建立、断开,消息接收、发送,通信过程中的异常状况 , 数据的编码、解码,序列化、反序列化,做一些比较底层的工作
Exchanger , ExchangeHandler 进行信息交换,单向的通信进行服务调用
ExchangerServer 服务端抽象层,实现可以是 Netty , Mina , Grizzly 的服务端
Transporter 、 Exchanger 、 ExchangerServer 都是和网络通信相关
服务暴露的 ip 地址,dubbo 默认是用 InetAddress.getLocalHost() 来获取本机IP地址 (这是从系统检索主机名称,然后将该名称解析为InetAddress), 如果要指定 IP 需要在 <dubbo:protocol host="" /> 进行配置
服务注册中心暴露
服务注册到注册中心的过程是使用服务的 URL 字符串向注册中心进行注册。可用的实现有 dubbo , multicast , zookeeper , redis 。 注册中心要做的事情有服务的注册、发现、对已注册的节点进行数据的同步
0 条评论
下一页