Dubbo是什么
dubbo是一款高性能的RPC(远程过程调用)框架,由阿里开发,后来开源捐献给了Apache
Dubbo的分层图
dubbo整体可用分为三层:business,rpc,remoting
business:就是我们的业务逻辑层提供业务数据展示与更新
rpc:提供了服务暴露,服务引用,远程调用,集群容错,路由过滤,负载均衡,代理对象等
remoting:网络传输协议和数据转换封装
Dubbo的工作原理
1.服务启动,provider和consumer根据配置信息,连接到注册中心register,分别向注册中心注册和订阅服务
2.register根据订阅关系,将provider信息通知给consumer,consumer收到register发送的消息将provider保存到本地,当provider配置发生变更会通知给consumer,consumer会刷新provider信息。
3.consumer根据路由,负载均衡选择一个provider,再根据容错策略,经过filter将调用次数和时间记录再内存,定时发送给monitor,最后通过invoke远程调用provider。
4.provider收到consumer的远程调用信息,进行反序列化获取到接口对应的实例对象调用接口,最后进行序列化返回给consumer。
Dubbo为什么使用invoke代理对象进行调用
主要为了封装接口调用过程,远程调用可以达到看上去与本地调用过程一样。简单,明了。封装成包装类。这样可以在调用过程中进行服务的路由,容错,负载,监控以及缓存等策略。
服务暴露过程
服务暴露基于spring容器的事件发布机制进行服务暴露的,读取配置信息组装url参数,生成invoke对象,包装成exporter对象,本地缓存,获取注册中心的register实例,将export注册到注册中心上。
服务暴露过程(源码级别)
首先服务暴露基于spring容器的事件发布机制进行暴露的。
1.ServiceBean实现了ApplicationListener接口重写了onApplicationEvent方法,在事件发布会调用onApplicationEvent()方法
onApplicationEvent():调用父类ServiceConfig的export()方法
export():读取配置信息,判断是否延迟加载,如果未配置直接暴露调用doexportUrls()方法
doexprotUrls():获取注册中心地址,多协议进行多暴露调用doExportUrlsFor1Protocol()方法
doExportUrlsFor1Protocol():组装参数存储到map中,根据map的参数及协议构造出url,获取url中的scope参数,此参数是判断本地暴露还是远程暴露,如果scope="local"为本地暴露,如果scope="remote"远程暴露,如果scope=""为空,本地和远程都进行暴露。
本地暴露调用exportLocal()方法:将url的协议更新为"injvm",通过ProxyFacotry创建接口的invoke实例也就是接口实现类,调用InJvmProtocol.export()包装成export对象,将export本地缓存到invokes中。
远程暴露:将协议和export封装到url中,如:url=dubbo://xxx?export=xxx这种形式,通过ProxyFactory获取invoke对象,调用RegisterProtocol.export()方法,接下来获取注册中心url和服务提供者的url,调用doLocalExport()远程暴露,封装Invoke调用DubboProtocol.export()方法,将invoke封装成DubboExport,紧接着进行判断server服务是否存在,是否开启等,如果第一次暴露会创建server服务,绑定端口,开启服务操作等,这些操作完成之后将exporter地址注册到注册中心上,将export本地缓存invokers中,至此服务暴露到此结束。
服务引用过程
服务引用有两个策略(懒汉式与饿汉式)
饿汉式:基于spring bean的生命周期回调机制,在实现InitializingBean的afterPropertieSet()方法进行引入
懒汉式:别的bean类在初始化时引入的rpc时,调用beanFactory获取ref会进行服务的引入。
通过getObject()方法进行服务引用,组装参数存储到map,本地引用创建invoke代理类即可,远程引用,将consumer注册到注册中心上,订阅该接口的providers,configurators,routes目录,通过通知信息,将routers,conifgurators,providers进行更新,providers信息就是该接口的所有export地址,将providers的各个export地址转换成invoke对象,这个过程会通过ip端口创建连接,将client实例封装在invoke中,cluster路由随机选出一个invoke实例,创建该invoke的代理对象,服务引用完成。
服务引用过程(源码级别)
两种方式引用都是通过ReferenceBean的getObject()方法中的get()方法,会调用父类的ReferenceConfig类的get()方法,读取配置,判断是ref引用是否为空,如果null进行初始化调用init()方法。
init():组装参数存储到map中,调用createProxy()方法生成代理对象。
createProxy():判断是否时本地引用,是否时peer-to-peer引用,注册中心引用。
本地引用:调用InJvmProtocol.refer()方法,通过父类AbstractProtocol.refer(),在通过InJvmProtocol.refer(),创建invoke对象,生成invoke代理对象,进行引用。
直连引用与远程引用,组装url参数,进行创建引用。调用RegisterProtocol.refer()方法,创建url信息,通过url获取注册中心实例,调用doRefer()方法,创建RegisterDerectory对象,设置注册中心实例,协议等,将consumer注册到注册中心上,订阅该接口的providers,routers,configurators的目录,当第一次启动或者该目录信息更新都会通知到RegisterDerectory的notify接口(RegisterDerectory实现了NotifyListener接口)。目录信息通知notify接口更新或重写本地invoke信息,通过providers的urls创建invoke,根据协议自适应扩展调用DubboProtocol.refer()方法,创建DubboInvoker,在创建DubboInvoker会先获取服务提供者的client(ExchangeClient)实例,通过getClients()方法获取,如果是共享客户端,获取即可,如果客户端为创建,进行初始化客户端,根据provider的ip,端口信息,与provider进行建立连接。创建DubboInvoer对象返回,redirect会持有所有的invoker对象,只需要引用某个一个即可,创建该invoke代理对象即可,至此服务引用完成。
服务调用过程
使用rpc代理对象调用某个接口方法,通过Cluster过滤匹配符合匹配规则的invoke,在通过负载均衡出一个invoke,通过client进行远程调用,这个过程会有容错机制,监控信息的filter上报,创建DefaultFuture 每个请求生成唯一的id存储到channel和future中,远程调用后将future存储到concurrenthashmap中,通过请求的id获取future进行get()获取返回的信息,反序列化等操作返回给调用者。
Dubbo的路由策略有哪些
服务调用过程中AbstractClusterInvoker抽象类中调用list()方法获取过滤后的invokers,在过滤过程中有可选的路由策略分别是:ConditionRouter(条件路由),TagRouter(标签路由),ScriptRouter(脚本路由)
Dubbo的负载均衡算法有哪些
服务调用过程中AbstractClusterInvoker调用loadBalance()方法获取配置的负载均衡算法,url配置的loadbalance参数。负载均衡算法有:
RandomLoadBalance:权重随机算法
RandomRobinLoadBalance:轮询算法
LastActiveLoadBalance:服务活跃度算法
ConsistentHashLoadBalance:一致性哈希算法
当然也可以自定义一些算法,只需要在META-INFO/dubbo/目录下创建文件基于dubbo的spi机制
Dubbo的容错机制策略有哪些
服务调用过程中,通过路由,获取负载均衡算法后,使用容错策略进行远程调用,
容错策略包含:
FailoverCluster:重试机制。调用失败后,重新选择一个client进行重试
FailbackCluster:失败自动恢复,调用失败,记录在内存的失败列表中,会有单独的线程进行异步重试调用一次,调用方无感知
FailfastCluster:快速失败。调用失败,立刻抛出异常
FailsafeCluster:安全失败,调用失败,捕获异常,返回空结果,忽略此次调用,将调用链记录在日志上,
ForkingCluster:并行策略,用向所有的服务提供方发出请求,有一个返回有结果就立刻返回数据
BroadcastCluster:广播策略,调用向所有的服务提供方发出请求,有一个发生异常此次调用认为失败的。
序列化有哪些
Hessian,dubbo,fastJson,java序列化
支持的协议
dubbo协议:传输数据量小,在一百k以内,但要求并发量很高。consumer与provider维持一个长连接,持续发送请求,配合着NIO异步通信,可以支持高并发量的请求
rmi协议:使用java序列化,多个短连接,使用与消费者与生产者差不多情况,一般使用文件传输
hessian协议:多个短连接,适用于服务提供者多于服务消费者。
redis协议:基于redis实现rpc协议
http协议:基于http表单提交的远程调用协议,短连接,传入参数大小混合,生产者多于消费者。
Dubbo的SPI
要知道dubbo的spi就需要了解java的spi机制,java只是使用自定义的扩展实现类需要在resource目录下创建META-INF/services目录,创建一个要实现接口的全限定名作为文件名称,内容是实现该接口的类全限定名称。要使用某个实现类,需要将该文件下的所有类都加载,循环遍历实现类动态判断要使用的哪个实现类。由此可以得出结论,需要将配置的实现类都要进行实例化,如果初始化某个实现类比较耗资源,但是实际项目中又用不上它,就会产生资源的浪费。正因为此原因Dubbo在此基础上进行了扩展,按需加载,通过key=xxx具体的实现类,kv形式,通过key找对应的实现类,约定的目录有是哪个META-INF/services/,META-INF/dubbo(用户自定义),META-INF/dubbo/internal/(dubbo内部使用的),dubbo的spi还实现类Adaptive注解,在使用该注解的接口方法上会生成一个字节码的代理类通过名称动态的获取接口实现类。