• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Dubbo相关

武飞扬头像
土豆rose
帮助1

看过dubbo源码?

1、服务调用过程

  • 最外层是Mock逻辑,调用前,调用后进行Mock
  • 从服务目录中,根据当前调用的方法和路由链,筛选出部分服务Invoker(DubboInvoker)
  • 对服务Invoker进行负载均衡,选出一个服务Invoker
  • 执行Filter链
  • AsyncToSyncInvoker完成异步转同步,因为DubboInvoker的执行是异步非阻塞的,所以如果是同步调用,则会在此处阻塞,知道拿到响应结果
  • DubboInvoker开始异步非阻塞的调用
  • HeaderExchangeChannel中会阻塞timeout的时间来等待结果,该timeout就是用户在消费端所配置的timeout

2、限流算法

1、一般的限制每秒多少请求量 ,用信号量来做,相当于线程池

2、TPS ,Dubbo默认使用令牌桶算法实现限流。某段时间内,桶里面只能放进n个令牌,然后来一个请求就减少一个令牌,如果桶里面的令牌没有了,则不能继续执行请求。限流通过com.alibaba.dubbo.rpc.filter.TpsLimitFilter实现。

3、负载均衡算法

1、随机   

  a、根据权重加总计算出总权重10

  b、随机一个10以内的数字

  c、轮询所有的invokers,然后拿随机值offset-每一个invoker的权重

  d、当结果小于0时,直接返回这个invoker即可

2、轮询(权重一样,就取余的方式获取到invoker,权重不同按照按公约后的权重设置轮循比率)

3、一致性hash(利用缓存的hash思想 每个privoder 有一个hash值存在一个圆环上,范围是0-2的32次方,然后key的hash)

4、最少活跃数(每个provider维护了一个active,调用 1 调用完成-1,轮询所有的invokers,取最小的active,如果active相同,就看权重,权重相同,就随机)

Dubbo动态代理:

1、javassist(默认)

2、jdk动态代理

JavassistProxyFactory 来创建引用服务的代理,JavassistProxyFactory 源码如下:

public class JavassistProxyFactory extends AbstractProxyFactory {
// 获取代理
public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    return (T) Proxy.getProx

y(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
    final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ?             proxy.getClass() : type);
    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);
    }
};
}
}

JavassistProxyFactory的getProxy方法看似跟jdk生成动态代理一样, 但这里的Proxy类不是jdk的类,而是dubbo自写的Proxy类(com.alibaba.dubbo.common.bytecode.Proxy),该类可利用javassist工具对接口生成代理代码(dubbo是不能对非接口类进行代理的)

Cglib和jdk动态代理的区别?

1、Jdk动态代理:利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理

2、 Cglib动态代理:利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理(通过ASM框架生成字节码变相执行Java代码,在JVM中程序执行不一定非要写java代码,只要能生成java字节码,jvm并不关心字节码的来源)

Dubbo 分层:

1、service层 接口层,主要provider和consumer 留给我们来实现的

2、config 层:提供一些配置文件可以来配置

3、proxy 层:代理层,无论时consumer 还是provider都会给你生成代理

4、registry层:provider注册自己作为一个服务,consumer就可以找到

5、cluster层:provider可以部署在多台机器,多个provider就组成了一个集群

6、monitor层:consumer调用provider 调用了多少次等统计信息

7、protocol层:负责provider额consumer之间的网络通信

8、exchange层:信息交换

9、serialize:序列化

Dubbo支持的协议

1、dubbo :基于长连接、NIO、 序列化是hessian

2、http:基于json序列化

3、rmi

4、webservice:基于xml文本的序列化

Dubbo负载均衡策略:

1、随机   

  a、根据权重加总计算出总权重10

  b、随机一个10以内的数字

  c、轮询所有的invokers,然后拿随机值offset-每一个invoker的权重

  d、当结果小于0时,直接返回这个invoker即可

2、轮询(权重一样,就取余的方式获取到invoker,权重不同按照按公约后的权重设置轮循比率)

3、一致性hash(利用缓存的hash思想 每个privoder 有一个hash值存在一个圆环上,范围是0-2的32次方,然后key的hash)

4、最少活跃数(每个provider维护了一个active,调用 1 调用完成-1,轮询所有的invokers,取最小的active,如果active相同,就看权重,权重相同,就随机)

详细解释:

Random LoadBalance(默认使用)

  • 随机,按权重设置随机概率。
  • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
  • 看方法的细节就是首先遍历每个提供服务的机器,获取每个服务的权重,然后累加权重值,判断每个服务的提供者权重是否相同,如果每个调用者的权重不相同,并且每个权重大于0,那么就会根据权重的总值生成一个随机数,再用这个随机数,根据调用者的数量每次减去调用者的权重,直到计算出当前的服务提供者随机数小于0,就选择那个提供者!另外,如果每个机器的权重的都相同,那么权重就不会参与计算,直接选择随机算法生成的某一个选择,完全随机。可以看出,随机调用法,

RoundRobin LoadBalance

  • 轮询,按公约后的权重设置轮询比率。
  • 存在慢的提供者类即请求问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
  • 轮询调用,轮询调用的过程主要是维护了局部变量的一个LinkdesHashMap(有顺序的Map)去存储调用者和权重值的对应关系,然后遍历每个调用者,把调用者和当前大于0的权重值放进去,再累加权重值。还有一个全局变量的map,找到第一个服务调用者,首先是找到每个服务的key值和method,这里可以理解为标识第一个调用者的唯一key,然后再给它对应的值保证原子性的 1(AtomicPositiveInteger是原子的),再对这个值取模总权重,再每次对其权重值-1,知道它取模与总权重值等于0就选择该调用者,可以称之为"降权取模"(只是一种的计算层面,而不是真正降权)。总结:轮询调用并不是简单的一个接着一个依次调用,它是根据权重的值进行循环的。

LeastActive LoadBalance

  • 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
  • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
  • 最少活跃数调用法:这个方法的主要作用根据服务的提供者的运行状态去选择服务器,主要的思路就是遍历每个调用者,然后获取每个服务器的运行状态,如果当前运行的运行状态小于最小的状态-1,把它保存在leastIndexs中的第一个位置,并且认定所有的调用者权重都相同,然后直接返回那个调用者(这里的逻辑是:找到最少活跃数(在代码层反应就是:active的值))。如果计算出的权重值和最少的权重值相同,那么把它保存在leastIndexs数组里面,累加权重值,如果当前的权重值不等于初始值firstWeight,那么就认定不是所有的调用者的权重不同。然后再遍历lestIndexs,取权重累加值的随机数生成权重偏移量,在累减它,到它小于0的时候返回那个调用者。如果这些都不符合,就从leastIndexs随机选一个index,返回那个调用者!

ConsistentHash LoadBalance

  • 一致性Hash,相同参数的请求总是发到同一提供者。
  • 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其他提供者,不会引起剧烈变动。

Dubbo限流算法:

消费端通过配置acitves限制消费端调用的并发量,在达到最大并发量之后等待一个timeout时间再重试(计数器)。

服务端通过配置executes限制服务端接口的线程最大数量,达到最大数量之后直接抛出异常(信号量)。

2、,Dubbo默认使用令牌桶算法实现限流。某段时间内,桶里面只能放进n个令牌,然后来一个请求就减少一个令牌,如果桶里面的令牌没有了,则不能继续执行请求。限流通过com.alibaba.dubbo.rpc.filter.TpsLimitFilter实现。

public boolean isAllowable() {

// 获取现在的时间
long now = System.currentTimeMillis();

// 当经过了interval时间间隔
if (now > lastResetTime interval) {

// 重新设置token令牌的个数
token.set(rate);

// 从现在开始,经过interval的时间
lastResetTime = now;
}

// 获取令牌的值
int value = token.get();

boolean flag = false;

// 使用CAS实现乐观锁
while (value > 0 && !flag) {

// 能够执行请求,则令牌减一
flag = token.compareAndSet(value, value - 1);

value = token.get();
}

return flag;
}

DUBBO集群容错:

1、另一台重试

2、直接报错

3、并行请求多台机器,哪一个先返回就用哪个

4、dubbo后台记录日志做补偿,不报错也不返回

Dubbo 服务治理

    1、dubbo链路图

    2、服务访问qps及执行时间

    3、服务调用失败报警

    4、服务调用权限控制

Dubbo 服务降级

 服务A调用服务B 失败了好几次  ,不行就走降级(将接口开关mock设置为true,自己实现一个xxMock来实现一个模拟返回)

Dubbo失败重试超时时间(可配置失败重试3次 超时时间一般200ms)

如何保证幂等性:

  (1) redis 来一个标识 标识已经支付过 key是订单id,value是支付标识

(2)mysql 唯一索引

如何设计一个RPC框架:

RPC 本质上就是一个远程调用,那肯定就需要通过网络来传输数据。虽然传输协议可以有多种选择,但考虑到可靠性的话,我们一般默认采用 TCP 协议。为了屏蔽网络传输的复杂性,我们需要封装一个单独的数据传输模块用来收发二进制数据,这个单独模块我们可以叫做传输模块

用户请求的时候是基于方法调用,方法出入参数都是对象数据,对象是肯定没法直接在网络中传输的,我们需要提前把它转成可传输的二进制,这就是我们说的序列化过程。但只是把方法调用参数的二进制数据传输到服务提供方是不够的,我们需要在方法调用参数的二进制数据后面增加“断句”符号来分隔出不同的请求,在两个“断句”符号中间放的内容就是我们请求的二进制数据,这个过程我们叫做协议封装

传输模块:TCP传输。

协议模块:协议包、序列化、解压缩。

集群模块:服务发现、连接管理、负载均衡、路由、容错、限流、熔断、配置管理。

bootstrap模块: 动态代理(像调用本地方法一样调用远程服务) 链路追踪。

协议:怎么设计可扩展且向后兼容的协议?

RPC 请求在发送到网络中之前,他需要把方法调用的请求参数转成二进制;转成二进制后,写入本地 Socket 中,然后被网卡发送到网络设备中

但在传输过程中,RPC 并不会把请求参数的所有二进制数据整体一下子(指时间短暂或动作迅速)(指时间短暂或动作迅速)发送到对端机器上,中间可能会拆分成好几个数据包,也可能会合并其他请求的数据包

为了避免语义不一致的事情发生,我们就需要在发送请求的时候设定一个边界,然后在收到请求的时候按照这个设定的边界进行数据分割。这个边界语义的表达,就是我们所说的协议。

协议分为协议头和协议体

协议头:协议标识、协议长度、消息ID、消息类型、序列化方式

定长的协议头有一个问题:假设你设计了一个 88Bit 的协议头,其中协议长度占用 32bit,然后你为了加入新功能,在协议头里面加了 2bit,并且放到协议头的最后。升级后的应用,会用新的协议发出请求,然而没有升级的应用收到的请求后,还是按照 88bit 读取协议头,新加的 2 个 bit 会当作协议体前 2 个 bit 数据读出来,但原本的协议体最后 2 个 bit 会被丢弃了,这样就会导致协议体的数据是错的

所以我们需要设计一个可扩展的协议头,其关键在于让协议头支持可扩展,扩展后协议头的长度就不能定长了。那要实现读取不定长的协议头里面的内容,在这之前肯定需要一个固定的地方读取长度,所以我们需要一个固定的写入协议头的长度。整体协议就变成了三部分内容:固定部分、协议头内容、协议体内容

如何选择一个序列化框架?

1、JDK 原生序列化

JDK 自带的序列化机制对使用者而言是非常简单的。序列化具体的实现是由 ObjectOutputStream 完成的,而反序列化的具体实现是由 ObjectInputStream 完成的。序列化过程就是在读取对象数据的时候,不断加入一些特殊分隔符,这些特殊分隔符用于在反序列化过程中截断用

2、JSON(是一种文本型序列化框架)

JSON缺点:

JSON 进行序列化的额外空间开销比较大,对于大数据量服务这意味着需要巨大的内存和磁盘开销;JSON 没有类型,但像 Java 这种强类型语言,需要通过反射统一解决,所以性能不会太好

3、Hessian

Hessian 是动态类型、二进制、紧凑的,并且可跨语言移植的一种序列化框架。Hessian 协议要比 JDK、JSON 更加紧凑,性能上要比 JDK、JSON 序列化高效很多,而且生成的字节数也更小。

Hessian缺点(官方版本对 Java 里面一些常见对象的类型不支持):

Linked 系列,LinkedHashMap、LinkedHashSet 等,但是可以通过扩展 CollectionDeserializer 类修复;

Locale 类,可以通过扩展 ContextSerializerFactory 类修复;

Byte/Short 反序列化的时候变成 Integer

4、Protobuf

Protobuf  是 Google 公司内部的混合语言数据标准,是一种轻便、高效的结构化数据存储格式,可以用于结构化数据序列化,支持 Java、Python、C 、Go 等语言

优点:

序列化后体积相比 JSON、Hessian 小很多;IDL 能清晰地描述语义,所以足以帮助并保证应用程序之间的类型不会丢失,无需类似 XML 解析器

序列化反序列化速度很快,不需要通过反射获取类型

消息格式升级和兼容性不错,可以做到向后兼容

缺点:需要预编译过程,依赖IDL接口定义语言

5、Protostuff 

可以直接对 Java 领域对象进行反 / 序列化操作,在效率上跟 Protobuf 差不多,生成的二进制格式和 Protobuf 是完全相同的,可以说是一个 Java 版本的 Protobuf 序列化框架

NIO网络模型:

netty 怎么实现零拷贝:

     Netty  的  ByteBuffer 可以采用 Direct Buffers,使用堆外直接内存进行 Socket 的读写操作,最终的效果与我刚才讲解的虚拟内存所实现的效果      是一样的。

Netty 是怎么对数据操作进行优化的呢?

     Netty 提供了 CompositeByteBuf 类,它可以将多个 ByteBuf 合并为一个逻辑上的  ByteBuf,避免了各个 ByteBuf 之间的拷贝。

     ByteBuf 支持 slice 操作,因此可以将 ByteBuf 分解为多个共享同一个存储区域的 ByteBuf,避免了内存的拷贝。

     通过 wrap 操作,我们可以将 byte[] 数组、ByteBuf、ByteBuffer  等包装成一个 Netty ByteBuf 对象, 进而避免拷贝操作。

netty底层实现就是Reactor模式如下:

Reactor 模式:

学新通

reactor 模式的核心自然是 Reactor 这个类,其中 register_handler() 和 remove_handler() 这两个方法可以注册和删除一个事件处理器

handle_events() 方式是核心,这个方法的核心逻辑如下:首先通过同步事件多路选择器提供的 select() 方法监听网络事件,当有网络事件就绪后,就遍历事件处理器来处理该网络事件。由于网络事件是源源不断的,所以在主程序中启动 Reactor 模式,需要以 while(true){} 的方式调用 handle_events() 方法。

netty中的线程模型:

                                                                                                                                                                                                                 学新通                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhggfehk
系列文章
更多 icon
同类精品
更多 icon
继续加载