Skip to content

Latest commit

 

History

History
319 lines (174 loc) · 11.2 KB

dev.md

File metadata and controls

319 lines (174 loc) · 11.2 KB

开发文档

rpc-http

问题1

简述

在连接池的注册时候,通过List直接存不同的端点的connection,在随机取得时候并不能保证取到的connection包含了将要调用的服务。

解决方案

在后面rpc-netty中进行zookeeper作为注册中心的时候得以解决:

  1. 通过自旋等待,超时以及重试机制,防止服务上线慢导致的服务获取不到,或者长时间等待服务导致系统客户端阻塞问题。
  2. 通过Registry中对ConcurrentHashMap(<rpcPeer,handler>)的反转(<service,List) 进行真正的负载均衡
  3. 通过ConcurrentHashMap(<rpcPeer,handler>)保持与多个服务节点之间的连接,同时rpcPeer中增加了该节点提供的服务集合 这样方便后续实现根据服务查找的时候的对ConcurrentHashMap(<rpcPeer,handler>)的反转(<service,List)。 也即 一个服务 可以由 多个服务节点进行提供。然后进行负载均衡操作。

时间线

2023/4/12-2023/5/6

rpc-netty

问题1

简述

RpcClientHandler 中 send进行远程调用的时候,channel空指针

解决方案

发现 channelRegistered 时候注册的channel,在后续客户端invokeRemote的时候,new 了一个新的RpcClientHandler,导致最后没有触发channelRegistered channel也就没有保存.

时间线

2023/4/12-2023/4/13

问题2

简述

性能差,单线程阻塞执行

如果是在同一个线程中执行,channelRead0返回前会一直阻塞;如果是在不同的线程中执行,channelWrite会把请求发送到发送缓冲区,然后立即返回,不会阻塞,而channelRead0在收到响应后才会返回。一般而言,Netty使用的是事件驱动模型,在一个线程中执行,因此channel.writeAndFlush()会一直阻塞到channelRead0返回。但是,如果你在代码中使用了异步IO或者采用了不同的事件驱动模型,那么这个问题的答案就会有所不同。

难点

  • 当我异步直接释放调channel,那么我怎么保存当前状态呢?
  • 作为调用者client,那么我又什么时候去获取返回结果呢?

解决方案

异步执行,模仿JUC的Future.get()来实现,那么就需要保存请求id的所对应的Future

时间线

2023/4/12 - 2023/4/20

问题3

简述

客户端请求发出之后,服务器解码,始终报错空指针

解决方案

kryo反序列化的时候 readObjectOrNull 换成 readObject

时间线

2023/4/13 - 2023/4/13

问题4

简述

每次序列化和反序列化时都需要重新创建一个 Kryo 实例,这可能会带来一些性能上的问题。

解决方案

将 Kryo 实例作为一个单例对象,并通过对象池等机制来重复使用。

时间线

2023/4/13 - 2023/4/14

问题5

简述

server启动之后,对client开启Debug模式,会导致Kryo反序列化存在循环依赖时,会出现堆栈溢出(StackOverflow)的问题。

问题解决

在 debug 模式下,由于开启了很多优化选项,包括类加载、方法执行等等,可能会使反射行为与正常运行时的行为有所不同,因此可能会导致反射失败。其中最常见的问题就是由于JVM启用了类的懒加载或者JIT优化等机制,导致反射调用时还未被加载或优化的类或方法并不可见的问题。

一个例子是,因为Java动态编译器(JIT)在运行时通过对代码进行优化来提高执行速度。如果代码正在被动态编译,那么它的类可能会被临时解除锁定,从而难以在运行时用反射获得该类的数据和方法。

使用加入了一定插桩(如:ASM、AspectJ 等)的方法来实现反射,这样可以更加稳定地进行反射操作,并且不易受到JVM的影响而导致反射失败。

时间线

2023/4/13 - 2023/4/13

问题6

简述

拆包:由于网络不稳定或者传输的数据太大,将数据进行分段传输,如2000字节拆成1000字节和1000字节 粘包:两个或多个数据包结合在一起同时发送,如1000 和 1000 组合成2000字节进行发送。主要是出现在一下两种情况

  • 网络发生拥堵,那么网络传输中拥塞机制可能会采用拥塞控制,将小数据合并成大数据包,来减少网络负载。也就是粘包
  • TCP协议中的发送窗口、接收窗口,即缓冲区。如果待发送的数据小于缓冲区,那么可能后续的数据进行合并之后把整个缓冲区发出去;如果待取的数据 小于缓冲区未被取,可能会和后续到来的数据一起被取走;

主要是由于TCP对数据进行拆分和组合而引起的。

问题解决

在Decoder中先读取网络字节流中的前4个字节来获得一个整数length,该整数表示后面的数据包的长度。

  • 如果后续的数据包长度小于length,就暂时不处理。
  • 如果长度足够,就一次性读取bytes字节数组,并通过反序列化的方式将其还原成具体的对象。

这样做的好处是,即使在数据传输过程中发生拆包和粘包的情况,代码也可以通过前4个字节显式地指定包的长度来避免数据解析错误。 比如,如果在传输中发生粘包,也就是将两个或多个数据包粘到了一起,那么前4个字节仍然在第一个包的开头,可以用来正确地解析第一个数据包。 如果在传输中发生了拆包,即将一个数据包拆成了两个或更多个片段,那么第一个片段不足以构成一个完整的数据包,因此可以暂时不处理。等到后续的片段到达,再按照前4个字节中指定的长度来组合成一个完整的数据包,并完成解析。

TODO 如果查过GB的大小呢,能够实现断点重传吗

时间线

2023/4/11 - 2023/4/11

问题7

简述

调用非远程服务的方法的时候,动态代理会在invoke去请求远程服务,导致服务找不到报空指针异常

问题解决

通过invoke方法进行调用方法的过滤得以解决

时间线

2023/4/21 - 2023/4/21

问题8

简述

远程调用的时候时而返回空指针,时而返回正常。

问题解决

在远程调用的,RpcFuture的实现基于AQS,初始状态也就是未完成状态应该是0,完成是1;原先设置反了,导致 任务未结束就返回了。

时间线

2023/4/21 - 2023/4/21

问题9

简述

大量线程阻塞,最后宕机。

问题解决

将代码顺序如下进行修改,之前的代码先发送后进行记录future:那么如果我阻塞发送之后,后续请求返回了 我还没记录futureMap,那么是不是最后很多线程都会一直阻塞了,因为future一直是null,也就 无法调用future.done,也无法释放client了

RpcFuture future = new RpcFuture ();
future.setRequest (request);
futureMap.put (request.getServiceId (), future);
try {
    channel.writeAndFlush (request).sync ();
} catch (InterruptedException e) {
    throw new RuntimeException (e);
}

时间线

2023/4/22 - 2023/4/23

问题10

简述

对有返回大量数据的接口进行远程调用的时候,出现了 com.esotericsoftware.kryo.io.KryoBufferUnderflowException: Buffer underflow.

问题解决

rpc测试过程中传输过大的对象,缩小对象,因为实际RPC只是过程调用,建议在远程服务直接做好处理

时间线

2023/4/24 - 2023/4/25

问题11

简述

在Zookeeper使用过程中,如何进行动态上下线监听

问题解决

通过PathChildrenCacheListener进行解决

时间线

2023/5/1 - 2023/5/3

问题12

简述

Handler如果在共享的时候,会出现并发写的问题吗?

问题解决

如果A、B两个线程通过ConcurrentHashMap获取的时候,显然是可以共享一个Handler的。哪怕A、B同时在write的时候其实也只是提交一个该handler的事件 放入了该handler的事件队列中,之后会按顺序进行处理,所以不会有问题。但是关键在于后面执行的时候,异步返回如何能够准确知道是哪个请求的响应值?由于之前的 异步RPCFuture中存储的RequestId,所以不会出现问题。

时间线

2023/5/4 - 2023/5/4

问题13

简述

在进行Zookeeper引入之后,进行测试的时候出现 org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode = ConnectionLoss for /services/--558691940

问题解决

zookeeper连接写错

时间线

2023/5/5 - 2023/5/5

问题14

简述

在更新连接池的时候,同时存在着服务调用的时候,handler优先谁使用呢?即读写优先问题。

如果读优先,很明显当下线之后,没有立即更新连接池就是错误的行为。

问题解决

其实就是动态上下线的几个场景:

访问已下线的服务

在这种情况下,消费者会一直尝试连接已经下线的服务,直至出现超时或者连接失败的情况。解决此类问题需要消费者实现相应的容错机制,如重试或切换至其他可用的服务。

时间线

2023/5/6 - 2023/5/6

服务提供者正在下线,但是消费者还在访问该服务。

在这种情况下,消费者可能会收到异常或超时等错误信息,并且无法正常访问服务。解决此类问题需要消费者实现相应的容错机制,如切换至其他可用的服务,或者等待服务上线后再次访问。

时间线

2023/5/6 - 2023/5/6

服务提供者上线时间较慢,但是消费者已经开始访问该服务。

在这种情况下,消费者可能会一直等待服务上线,导致访问出现长时间阻塞的情况。解决此类问题需要消费者实现相应的超时控制机制,如等待一定时间后再切换至其他可用的服务。 这里通过超时机制以及重试进行解决。

时间线

2023/5/5 - 2023/5/6

综上,在通信过程中可能会出现一些问题,需要消费者实现相应的容错机制来应对,保证系统的稳定性和可靠性。

问题15

简述

虽然已经通过了zookeeper实现了动态上下线,但是当很有可能某些节点其实已经“死”了,即不能正常提供服务了 这些时候空闲节点占据着网络带宽,显然不合适。

问题解决

通过netty管道中添加handler实现心跳机制

心跳机制

心跳:在 TPC 中,客户端和服务端建立连接之后,需要定期发送数据包,来通知对方自己还在线,以确保 TPC 连接的有效性。如果一个连接长时间没有心跳,需要及时断开,否则服务端会维护很多无用连接,浪费服务端的资源。

Netty 已经为我们提供了心跳的 Handler:IdleStateHandler。当连接的空闲时间(读或者写)太长时,IdleStateHandler 将会触发一个 IdleStateEvent 事件,并传递的下一个 Handler(其实就是去调用userEventTriggered方法)。我们可以通过在 Pipeline Handler 中重写 userEventTrigged 方法来处理该事件,注意我们自己的 Handler 需要在 IdleStateHandler 后面。 根据构造函数给的 读写空闲时间 去决定初始化哪些定时任务,分别是:ReaderIdleTimeoutTask(读空闲超时任务)、WriterIdleTimeoutTask(写空闲超时任务)、AllIdleTimeoutTask(读写空闲超时任务)。

时间线

2023/5/8 - 2023/5/9