dubbo协议
dubbo负载均衡策略及实现方式
-
Random 随机
-
RoundRobin 轮循
平均分布,但是存在请求累积的问题
-
LeastActive 最少活跃,处理能力慢的服务接收更少的请求
每个服务维护一个int类型活跃数计数器,开始处理时+1处理完成时-1。当A服务开始处理请求,计数器加1,此时A还未处理完成。而B服务接受到请求后很快处理完毕。那么A、B的活跃数分别是1、0。当又产生了一个新的请求,则选择B服务(B活跃数最小),减少慢服务收的请求
-
ConstantHash 一致性Hash 相同参数请求总是发到同一提供者,一台机器宕机,可以基于虚拟节点,分摊至其他提供者
基于TreeMap<Long, Invoker<T>> 及其tailMap方法实现(TreeMap是基于key做了排序,tailMap方法返回大于等于key的SortedMap类型元素集合),默认为每个节点生成160个虚拟节点,为每个节点包括虚拟节点计算出一个hash值作为key,获取时,默认根据方法的第一个参数(eg: find(String name, int age) -> 取name进行hash)进行hash,随后调用tailMap(key)方法获取,如果返回空则取第一个(已这种方式形成逻辑环)
private static final class ConsistentHashSelector<T> { private final TreeMap<Long, Invoker<T>> virtualInvokers; // 虚拟结点 private final int replicaNumber; // 副本数 private final int identityHashCode;// hashCode private final int[] argumentIndex; // 参数索引数组 public ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) { // 【创建TreeMap来保存结点】 this.virtualInvokers = new TreeMap<Long, Invoker<T>>(); // 生成调用结点HashCode this.identityHashCode = System.identityHashCode(invokers); // 获取Url dubbo://169.254.90.37:20880/service.DemoService?anyhost=true&application=srnt&check=false&dubbo=2.8 URL url = invokers.get(0).getUrl(); // 获取所配置的结点数,如没有设置则使用默认值160 this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160); // 获取需要进行hash的参数数组索引,【默认对第一个参数进行hash】 String[] index = Constants.COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, "hash.arguments", "0")); argumentIndex = new int[index.length]; for (int i = 0; i < index.length; i ++) { argumentIndex[i] = Integer.parseInt(index[i]); } // 创建虚拟结点 对每个invoker生成replicaNumber个虚拟结点,并存放于TreeMap中 for (Invoker<T> invoker : invokers) { for (int i = 0; i < replicaNumber / 4; i++) { // 根据md5算法为每4个结点生成一个消息摘要,摘要长为16字节128位。 byte[] digest = md5(invoker.getUrl().toFullString() + i); for (int h = 0; h < 4; h++) { long m = hash(digest, h); virtualInvokers.put(m, invoker);// 放入map } } } } private Invoker<T> sekectForKey(long hash) { Invoker<T> invoker; Long key = hash; // 若HashCode直接与某个虚拟结点的key一样,则直接返回该结点 if (!virtualInvokers.containsKey(key)) { // 若不在,找到一个最小上届的key所对应的结点。 SortedMap<Long, Invoker<T>> tailMap = virtualInvokers.tailMap(key); // 若存在则返回,例如hashCode落在图中[1]的位置 // 若不存在,例如hashCode落在[2]的位置,那么选择treeMap中第一个结点 // 使用TreeMap的firstKey方法,来选择最小上界。 if (tailMap.isEmpty()) { key = virtualInvokers.firstKey(); } else { key = tailMap.firstKey(); } } invoker = virtualInvokers.get(key); return invoker; } }
dubbo使用中遇到的问题
-
provider抛自定义异常,customer捕捉到的有时候正确,有时候是RuntimeException
dubbo异常处理机制
ExceptionFilter#invoke
1.如果是checked异常(Exception作为异常父类,是checked异常,要求必须try,RuntimeException作为其子类,是unchecked异常),直接抛出.很明显,我们的
HelloException
是RuntimeException
,不符合2.在方法签名上有声明,直接抛出.很明显,我们接口并未声明该异常,不符合
3.异常类和接口类在同一jar包里,直接抛出.很明显,我们的异常类是在common.jar的,接口是在api.jar的,不符合
4.是JDK自带的异常,直接抛出.很明显,这个
HelloException
是我们自定义的,不符合5.是Dubbo本身的异常(RpcException),直接抛出.很明显,这个
HelloException
是我们自定义的,和RpcException
几乎没有半毛钱关系.6.否则,包装成RuntimeException抛给客户端.因为以上5点均不满足,所以该异常会被包装成
RuntimeException
异常抛出(重要)为什么要这么设计
可能是由于序列化的原因,因为如果服务器抛出一个自定义异常,直接扔给客户端,客户端可能解析不了,第1步判断是否是检查异常,检查异常是dubbo框架内部的,所以客户端可见。第2步判断是否在方法上有声明该异常,如果有说明客户端对这个异常也是可见的,以下的都一样。
怎么解决问题
API.jar方法加上异常声明
-
有些版本(2.5)并发数高一些的话会出现大量客户端超时
dubbo有3种线程池,默认使用固定线程池,核心线程数是200,队列容量默认是0(JDK的是Integer.max),这就会导致超过200个任务并发执行的时候就会拒绝,2.5版本的拒绝策略是记录日志后抛异常,抛出的异常再次交给业务线程处理,此时业务线程池可能仍然是满的,再次拒绝,导致响应信息一直没有机会处理,客户端傻等到超时
2.6版本会catch拒绝异常,判断当前请求是否有返回值,有返回值的直接把异常信息写入响应,客户端不用再傻等
dubbo管理平台admin、监控平台monitor
监控平台
- 服务注册信息,包括提供者和消费者、注册中心信息
- 调用数据统计,包括调用耗时、QPS、调用和响应的折线图
实现原理:基于dubbo过滤器MonitorFilter,invoke方法调用时会收集调用相关的监控统计信息,并且每隔1分钟上报监控平台,
最终交给DubboMonitor做上报操作,定时上报的实现方式是用定时线程池ScheduledExecutorService.scheduleWithFixedDelay
监控中心
https://www.jianshu.com/p/2062a91502ac?utm_source=oschina-app
管理平台
- 【提供者】【消费者】查看服务提供者、消费者列表(机器ip),可操作禁用、启用
- 【权重调节】修改服务权重,【负载均衡】修改负载策略
- 【路由】配置路由策略,可以启用禁用,会设置一些匹配条件,比如针对某个方法,消费者ip、消费者应用名、过滤条件:服务提供者ip、端口
路由可以用来做应用隔离,即发版时把所有流量都打到一个节点上,等另一个节点发版完成,再切到新版上,再去发旧版 - 【动态配置】可以配置服务降级,非业务异常比如超时这种,可以设置返回结果 return null
管理平台
https://www.jianshu.com/p/6ce86645c462
dubbo注册了些什么
-
节点类型
dubbo / 服务接口路径 永久节点
providers 临时节点
consumers
-
服务信息 服务提供和服务消费注册信息不同
服务提供者:协议、服务器ip与端口、服务接口路径、接口方法名称集合、dubbo版本号、服务接口版本号
providers/dubbo://10.118.66.15:20880/org.spring.springboot.dubbo.DubboService?anyhost=true&application=provider&dubbo=2.5.3&interface=org.spring.springboot.dubbo.DubboService&methods=find&pid=13608&revision=1.0.0&side=provider×tamp=1566348434768&version=1.0.0
consumers/consumer://10.118.66.15/org.spring.springboot.dubbo.DubboService?application=consumer&category=consumers&check=false&dubbo=2.5.3&interface=org.spring.springboot.dubbo.DubboService&methods=getCitys,find&pid=5148&revision=1.0.0&side=consumer×tamp=1566375948555&version=1.0.0
SPI和API
SPI(Service Provider Interface)服务提供者接口,API应用程序接口
SPI是JDK接口和实现类绑定的一种机制,实现方式,在[资源目录] 的META-INF文件夹下提供一个以接口命名的文件,文件内容是接口实现类的路径,对应的文件加载器类(ServiceLoader)会到这个地方加载对应的文件
从概念上讲,API侧重于服务提供者,我有一个服务,对外暴露接口。SPI侧重服务调用者,我想要一个服务,我定义一个接口,具体实现有其他人定
个以接口命名的文件,文件内容是接口实现类的路径,对应的文件加载器类(ServiceLoader)会到这个地方加载对应的文件
从概念上讲,API侧重于服务提供者,我有一个服务,对外暴露接口。SPI侧重服务调用者,我想要一个服务,我定义一个接口,具体实现有其他人定