Dubbo
Dubbo架构图
第一层:service层,接口层,给服务提供者和消费者来实现的
第二层:config层,配置层,主要是对dubbo进行各种配置的
第三层:proxy层,服务代理层,透明生成客户端的stub和服务单的skeleton
第四层:registry层,服务注册层,负责服务的注册与发现
第五层:cluster层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务
第六层:monitor层,监控层,对rpc接口的调用次数和调用时间进行监控
第七层:protocol层,远程调用层,封装rpc调用
第八层:exchange层,信息交换层,封装请求响应模式,同步转异步
第九层:transport层,网络传输层,抽象mina和netty为统一接口
第十层:serialize层,数据序列化层
Dubbo支持的通信协议
协议名 | 序列化 | 连接 | 适用场景 |
---|---|---|---|
Dubbo协议 | hessian2 | 单一长连接,NIO异步通信 | 传输数据量很小(每次请求在100kb以内),但是并发量很高,消费者远大于提供者 |
rmi协议 | java二进制序列化 | 多个短连接 | 消费者和提供者数量差不多,文件的传输 |
hessian协议 | hessian序列化协议 | 多个短连接 | 传输数据量较大,提供者数量比消费者数量还多,文件的传输 |
http协议 | json序列化 | 多个短连接 | 需同时给应用程序和浏览器 JS 使用的服务 |
webservice | SOAP文本序列化 | 多个短连接 | 系统集成,跨语言调用 |
Dubbo不好用的地方
不好用的地方 | 说明 | 原因 | 解决方案 |
---|---|---|---|
参数及返回值需要实现Serializable接口 | 默认协议是Dubbo协议,通过netty传输,需要转换成二机制数据 | 实现Serializable接口 | |
参数及返回值不能自定义实现List, Map, Number, Date, Calendar等接口 | - | 默认协议是Dubbo协议,采用的是hessian序列化&反序列化方式,遇上以上接口时会做特殊处理,自定义实现类中的属性值都会丢失 | 使用java原生接口 |
父子类有相同属性时值丢失 | - | 默认采用hessian序列化,获取属性时,采用了map去重,但是读值时,根据序列化顺序,对于同名字段,子类的该字段值会被赋值两次,总是被父类的值覆盖,导致子类的字段值丢失 | 不要重名 |
自定义异常被包装成RuntimeException | - | dubbo提供端对异常进行了统一封装导致的 | 将自定义异常和接口类放到一个包中/方法签名上申明自定义异常 |
IP暴露问题 | Docker、双网卡、虚拟机等环境下,Dubbo默认绑定的IP可能并不是我们期望的正确IP | 跟Dubbo绑定IP默认行为有关 | 通过添加启动参数和配置解决 |
Data length too large | 请求或者响应的报文体长度超过了8k | dubbo协议适合小数据传输,建议更改协议 | |
线程耗尽 | Dubbo提供者线程池为fix类型,默认线程数为200,队列为0 | 解决业务耗时原因/扩容 |
Dubbo服务提供端异常统一处理逻辑
目的是为了保证抛出的异常,消费端都能识别,具体逻辑如下:
- 如果是checked异常(不是RuntimeException但是是Exception.java类型的异常),直接抛出;
- 在方法签名上有声明(例如String saySomething()throws MyException ),直接抛出;
- 异常类和接口类在同一jar包里,直接抛出;
- 是JDK自带的异常(全类名以java或者javax开头,例如java.lang.IllegalStateException),直接抛出;
- 是Dubbo本身的异常RpcException,直接抛出;
- 否则,Dubbo通过如下代码将异常包装成RuntimeException抛给客户端
Dubbo常用性能调优属性
注意表中参数与图中的对应关系:
1、当consumer发起一个请求时,首先经过active limit(参数actives)进行方法级别的限制,其实现方式为CHM中存放计数器(AtomicInteger),请求时加1,请求完成(包括异常)减1,如果超过actives则等待有其他请求完成后重试或者超时后失败;
2、从多个连接(connections)中选择一个连接发送数据,对于默认的netty实现来说,由于可以复用连接,默认一个连接就可以。不过如果你在压测,且只有一个consumer,一个provider,此时适当的加大connections确实能够增强网络传输能力。但线上业务由于有多个consumer多个provider,因此不建议增加connections参数;
3、连接到达provider时(如dubbo的初次连接),首先会判断总连接数是否超限(acceps),超过限制连接将被拒绝;
4、连接成功后,具体的请求交给io thread处理。io threads虽然是处理数据的读写,但io部分为异步,更多的消耗的是cpu,因此iothreads默认cpu个数+1是比较合理的设置,不建议调整此参数;
5、数据读取并反序列化以后,交给业务线程池处理,默认情况下线程池为fixed,且排队队列为0(queues),这种情况下,最大并发等于业务线程池大小(threads),如果希望有请求的堆积能力,可以调整queues参数。如果希望快速失败由其他节点处理(官方推荐方式),则不修改queues,只调整threads;
6、execute limit(参数executes)是方法级别的并发限制,原理与actives类似,只是少了等待的过程,即受限后立即失败;
Dubbo负载均衡策略
策略名 | 实现逻辑 |
---|---|
随机 | 生成随机值,判断落在哪个权重范围内 |
轮询 | 维护一个总调用次数,对总权重取模,判断落在哪个权重范围内 |
最少活跃数 | 维护每个服务器的活跃数,取最小值 |
一致性Hash |
Dubbo的集群及容错策略
容错策略名 | 说明 | 适用场景 |
---|---|---|
failover | 失败自动切换 | 读操作 |
failfast | 失败立即返回 | 写操作 |
failsafe | 失败则忽略 | 允许丢失的操作,例如日志 |
failback | 失败定时重发 | 消息队列 |
forking | 并行调用多个provider,某个成功就返回 | |
broadcast | 广播,逐个调用所有的provider |
Dubbo的动态代理
- 默认使用javassist动态字节码生成,创建代理类
java的spi机制的原理(ServiceLoader)
- 获取classloader
- 读取META-INF/services/+全限定接口名的文件内容
- 循环创建实现类对象并加入缓存
- 返回实现类对象集合
springboot的spi机制
- spring.factories文件
Dubbo的spi机制的使用
- 三个路径都可以放以接口全限定类为名的文件, META-INF/services/ 、META-INF/dubbo/和META-INF/dubbo/internal/
- 文件内容是键值对格式
- 接口需要用@SPI注解修饰
- 通过接口类找到一个 ExtensionLoader
- 通过ExtensionLoader.getExtension(key) 得到指定key的实现类实例
Dubbo的spi机制的原理
- 通过接口类找到一个 ExtensionLoader
- 通过定义key到实例缓存中查找是否存在该实例,如果存在则返回
- 实现类缓存中查找是否存在该类,没有则扫描约定的三个目录并加载
- 放射创建指定实现类对象
- 经过注入和包装后返回
Dubbo的服务降级
通过mock属性实现服务降级
Dubbo的服务熔断
无熔断机制,可通过hystrix实现。
Dubbo的服务限流
限流方式 | 使用客户端 | 标签 | 含义 |
---|---|---|---|
executes | 提供端 | service、method | 并发执行不能超过指定数 |
accepts | 提供端 | protocol | 指定协议的连接数不能超过指定数 |
actives | 提供端/消费端 | service、reference、method | 长链接时:最多可以处理的请求个数;短链接时:可以同时处理的短连接数量 |
connections | 提供端/消费端 | service、reference、method | 最大连接数 |
spring与Dubbo搭配使用时,在什么时候开始服务注册
- spring启动过程中刷新上下文操作时
- bean实例化结束后会发送上下文刷新完成事件
- Dubbo的ServiceBean类实现了ApplicationListener接口监听了该事件
- 故此时会去执行onApplicationEvent方法,也是在此时进行了服务注册
Dubbo 服务暴露的流程
- 根据配置得到 URL
- 通过 javassist 动态封装服务实现类,统一暴露出 Invoker 使得调用方便,屏蔽底层实现细节(ProxyFactory)
- 利用 Dubbo SPI 机制根据 URL 的参数选择对应的插件实现类(例如protocol)
- 然后将invoker封装成 exporter 存储起来,等待消费者的调用
- 最后将URL 注册到注册中心,使得消费者可以获取服务提供者的信息(ExporterListener)
Dubbo服务引用的过程(注册中心方式举例)
- 通过配置组成URL
- 向注册中心注册消费者,订阅节点(Directory)
- 监听注册中心获取提供者信息(Directory实现了NotifyListener接口)
- 通过SPI机制获取对应协议,生成Invoker(Directory.refer)
- Invoker中加入负载,容错等处理 (RegistryProtocol.merge)
- 最后通过动态代理封装得到代理类(ProxyFactory)
如何实现链路追踪
- 通过brave生成和传递traceId
- zipkin汇总traceId
- Logstash采集日志
- ElasticSearch+Kibana 可视化分析日志
dubbo的线程模型
- all 所有消息都派发到线程池
- direct 所有消息都不派发到线程池,全部在 IO 线程上直接执行
- message 只有请求、响应消息派发到业务线程池,其他连接断开事件/心跳等消息,直接在IO线程上执行
- execution 只请求消息派发到业务线程池,响应和连接、断开事件、心跳等消息,直接在 IO 线程上执行
- connection 在 IO 线程上,将连接、断开事件放入队列,有序逐个执行,其它消息派发到线程池
dubbo的线程模型图
dubbo停机的过程
- 收到 kill PID 进程退出信号,Spring 容器会触发容器销毁事件。
- provider 端会注销服务元数据信息(删除ZK节点)。
- consumer 会拉取最新服务提供者列表。
- provider 会发送 readonly 事件报文通知 consumer 服务不可用。
- 服务端等待已经执行的任务结束并拒绝新任务执行。
- TCP连接断开