开发文档
在连接池的注册时候,通过List直接存不同的端点的connection,在随机取得时候并不能保证取到的connection包含了将要调用的服务。
在后面rpc-netty中进行zookeeper作为注册中心的时候得以解决:
- 通过自旋等待,超时以及重试机制,防止服务上线慢导致的服务获取不到,或者长时间等待服务导致系统客户端阻塞问题。
- 通过Registry中对ConcurrentHashMap(<rpcPeer,handler>)的反转(<service,List) 进行真正的负载均衡
- 通过ConcurrentHashMap(<rpcPeer,handler>)保持与多个服务节点之间的连接,同时rpcPeer中增加了该节点提供的服务集合 这样方便后续实现根据服务查找的时候的对ConcurrentHashMap(<rpcPeer,handler>)的反转(<service,List)。 也即 一个服务 可以由 多个服务节点进行提供。然后进行负载均衡操作。
2023/4/12-2023/5/6
RpcClientHandler 中 send进行远程调用的时候,channel空指针
发现 channelRegistered 时候注册的channel,在后续客户端invokeRemote的时候,new 了一个新的RpcClientHandler,导致最后没有触发channelRegistered channel也就没有保存.
2023/4/12-2023/4/13
性能差,单线程阻塞执行
如果是在同一个线程中执行,channelRead0返回前会一直阻塞;如果是在不同的线程中执行,channelWrite会把请求发送到发送缓冲区,然后立即返回,不会阻塞,而channelRead0在收到响应后才会返回。一般而言,Netty使用的是事件驱动模型,在一个线程中执行,因此channel.writeAndFlush()会一直阻塞到channelRead0返回。但是,如果你在代码中使用了异步IO或者采用了不同的事件驱动模型,那么这个问题的答案就会有所不同。
- 当我异步直接释放调channel,那么我怎么保存当前状态呢?
- 作为调用者client,那么我又什么时候去获取返回结果呢?
异步执行,模仿JUC的Future.get()来实现,那么就需要保存请求id的所对应的Future
2023/4/12 - 2023/4/20
客户端请求发出之后,服务器解码,始终报错空指针
kryo反序列化的时候 readObjectOrNull 换成 readObject
2023/4/13 - 2023/4/13
每次序列化和反序列化时都需要重新创建一个 Kryo 实例,这可能会带来一些性能上的问题。
将 Kryo 实例作为一个单例对象,并通过对象池等机制来重复使用。
2023/4/13 - 2023/4/14
server启动之后,对client开启Debug模式,会导致Kryo反序列化存在循环依赖时,会出现堆栈溢出(StackOverflow)的问题。
在 debug 模式下,由于开启了很多优化选项,包括类加载、方法执行等等,可能会使反射行为与正常运行时的行为有所不同,因此可能会导致反射失败。其中最常见的问题就是由于JVM启用了类的懒加载或者JIT优化等机制,导致反射调用时还未被加载或优化的类或方法并不可见的问题。
一个例子是,因为Java动态编译器(JIT)在运行时通过对代码进行优化来提高执行速度。如果代码正在被动态编译,那么它的类可能会被临时解除锁定,从而难以在运行时用反射获得该类的数据和方法。
使用加入了一定插桩(如:ASM、AspectJ 等)的方法来实现反射,这样可以更加稳定地进行反射操作,并且不易受到JVM的影响而导致反射失败。
2023/4/13 - 2023/4/13
拆包:由于网络不稳定或者传输的数据太大,将数据进行分段传输,如2000字节拆成1000字节和1000字节 粘包:两个或多个数据包结合在一起同时发送,如1000 和 1000 组合成2000字节进行发送。主要是出现在一下两种情况
- 网络发生拥堵,那么网络传输中拥塞机制可能会采用拥塞控制,将小数据合并成大数据包,来减少网络负载。也就是粘包
- TCP协议中的发送窗口、接收窗口,即缓冲区。如果待发送的数据小于缓冲区,那么可能后续的数据进行合并之后把整个缓冲区发出去;如果待取的数据 小于缓冲区未被取,可能会和后续到来的数据一起被取走;
主要是由于TCP对数据进行拆分和组合而引起的。
在Decoder中先读取网络字节流中的前4个字节来获得一个整数length,该整数表示后面的数据包的长度。
- 如果后续的数据包长度小于length,就暂时不处理。
- 如果长度足够,就一次性读取bytes字节数组,并通过反序列化的方式将其还原成具体的对象。
这样做的好处是,即使在数据传输过程中发生拆包和粘包的情况,代码也可以通过前4个字节显式地指定包的长度来避免数据解析错误。 比如,如果在传输中发生粘包,也就是将两个或多个数据包粘到了一起,那么前4个字节仍然在第一个包的开头,可以用来正确地解析第一个数据包。 如果在传输中发生了拆包,即将一个数据包拆成了两个或更多个片段,那么第一个片段不足以构成一个完整的数据包,因此可以暂时不处理。等到后续的片段到达,再按照前4个字节中指定的长度来组合成一个完整的数据包,并完成解析。
TODO 如果查过GB的大小呢,能够实现断点重传吗
2023/4/11 - 2023/4/11
调用非远程服务的方法的时候,动态代理会在invoke去请求远程服务,导致服务找不到报空指针异常
通过invoke方法进行调用方法的过滤得以解决
2023/4/21 - 2023/4/21
远程调用的时候时而返回空指针,时而返回正常。
在远程调用的,RpcFuture的实现基于AQS,初始状态也就是未完成状态应该是0,完成是1;原先设置反了,导致 任务未结束就返回了。
2023/4/21 - 2023/4/21
大量线程阻塞,最后宕机。
将代码顺序如下进行修改,之前的代码先发送后进行记录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
对有返回大量数据的接口进行远程调用的时候,出现了 com.esotericsoftware.kryo.io.KryoBufferUnderflowException: Buffer underflow.
rpc测试过程中传输过大的对象,缩小对象,因为实际RPC只是过程调用,建议在远程服务直接做好处理
2023/4/24 - 2023/4/25
在Zookeeper使用过程中,如何进行动态上下线监听
通过PathChildrenCacheListener进行解决
2023/5/1 - 2023/5/3
Handler如果在共享的时候,会出现并发写的问题吗?
如果A、B两个线程通过ConcurrentHashMap获取的时候,显然是可以共享一个Handler的。哪怕A、B同时在write的时候其实也只是提交一个该handler的事件 放入了该handler的事件队列中,之后会按顺序进行处理,所以不会有问题。但是关键在于后面执行的时候,异步返回如何能够准确知道是哪个请求的响应值?由于之前的 异步RPCFuture中存储的RequestId,所以不会出现问题。
2023/5/4 - 2023/5/4
在进行Zookeeper引入之后,进行测试的时候出现 org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCode = ConnectionLoss for /services/--558691940
zookeeper连接写错
2023/5/5 - 2023/5/5
在更新连接池的时候,同时存在着服务调用的时候,handler优先谁使用呢?即读写优先问题。
如果读优先,很明显当下线之后,没有立即更新连接池就是错误的行为。
其实就是动态上下线的几个场景:
在这种情况下,消费者会一直尝试连接已经下线的服务,直至出现超时或者连接失败的情况。解决此类问题需要消费者实现相应的容错机制,如重试或切换至其他可用的服务。
2023/5/6 - 2023/5/6
在这种情况下,消费者可能会收到异常或超时等错误信息,并且无法正常访问服务。解决此类问题需要消费者实现相应的容错机制,如切换至其他可用的服务,或者等待服务上线后再次访问。
2023/5/6 - 2023/5/6
在这种情况下,消费者可能会一直等待服务上线,导致访问出现长时间阻塞的情况。解决此类问题需要消费者实现相应的超时控制机制,如等待一定时间后再切换至其他可用的服务。 这里通过超时机制以及重试进行解决。
2023/5/5 - 2023/5/6
综上,在通信过程中可能会出现一些问题,需要消费者实现相应的容错机制来应对,保证系统的稳定性和可靠性。
虽然已经通过了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