有人在维护社区嘛,对RPC有一点疑惑,可以帮忙解答嘛

嗝屁小孩纸 2022-10-17 22:03:22

我也学了一点RPC的知识,现在想做超时重试这一块,用的是Netty跟CompletableFuture来实现,不过我设置了超时get结果,在服务执行时间超过get设置的超时时长客户端会抛出这样的异常

21:28:16.957 cn.fyupeng.net.netty.client.NettyClientHandler [nioEventLoopGroup-3-4] - error occurred while invoking, error information:
java.lang.NullPointerException: null
	at cn.fyupeng.net.netty.client.UnprocessedRequests.complete(UnprocessedRequests.java:43)
	at cn.fyupeng.net.netty.client.NettyClientHandler.channelRead0(NettyClientHandler.java:48)
	at cn.fyupeng.net.netty.client.NettyClientHandler.channelRead0(NettyClientHandler.java:25)
	at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:105)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:310)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:284)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1412)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:943)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:141)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:645)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:580)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:497)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:459)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:886)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:748)
21:28:16.963 cn.fyupeng.net.netty.client.NettyClient [nioEventLoopGroup-3-4] - Error occurred while sending message: {}
java.nio.channels.ClosedChannelException: null
	at io.netty.channel.AbstractChannel$AbstractUnsafe.write(...)(Unknown Source)
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
	at com.sun.proxy.$Proxy0.sayHello(Unknown Source)
	at cn.fyupeng.Client.main(Client.java:36)
Caused by: java.util.concurrent.ExecutionException: java.nio.channels.ClosedChannelException
	at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
	at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1915)
	at cn.fyupeng.proxy.RpcClientProxy.invoke(RpcClientProxy.java:145)
	... 2 more
Caused by: java.nio.channels.ClosedChannelException
	at io.netty.channel.AbstractChannel$AbstractUnsafe.write(...)(Unknown Source)

客户端读取核心代码:

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RpcResponse msg) throws Exception {
        try {
            log.info(String.format("customer has received message: %s", msg));
            /**
             * 1. 取出 缓存在 AttributeKey 中 常量池ConstantPool 的 ConcurrentMap<String, RpcResponse>
             *  key 为 "rpcResponse"的 AttributeKey<RpcResponse>
             * 2. 底层原理是 putIfAbsent(name, tempConstant), 只第一次有效,下次不会 put, 只返回 第一次的值
             * 3. 一个 AttributeKey 类 分配 一个 常量池,多个AttributeKey 共享
             * 4. 多线程环境下 常量池中的 ConcurrentHashMap<String,T > 是共享的,并且是 线程安全的
             */
            //AttributeKey<RpcResponse> key = AttributeKey.valueOf(msg.getRequestId());
            //ctx.channel().attr(key).set(msg);
            System.out.println("unprocessedRequests" + unprocessedRequests);
            unprocessedRequests.complete(msg);
            //ctx.channel().close();
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("error occurred while invoking, error information:", cause);
        ctx.close();
    }

实现非阻塞手段

public class UnprocessedRequests {

   /**
    * k - request id
    * v - 可将来获取 的 response
    */
   private static ConcurrentMap<String, CompletableFuture<RpcResponse>> unprocessedResponseFutures = new ConcurrentHashMap<>();

   /**
    * @param requestId 请求体的 requestId 字段
    * @param future 经过 CompletableFuture 包装过的 响应体
    */
   public void put(String requestId, CompletableFuture<RpcResponse> future) {
      unprocessedResponseFutures.put(requestId, future);
   }

   /**
    * 移除 CompletableFuture<RpcResponse>
    * @param requestId 请求体的 requestId 字段
    */
   public void remove(String requestId) {
      unprocessedResponseFutures.remove(requestId);
   }

   public void complete(RpcResponse rpcResponse) {
      System.out.println("complete " + rpcResponse);
      CompletableFuture<RpcResponse> completableFuture = unprocessedResponseFutures.remove(rpcResponse.getRequestId());
      completableFuture.complete(rpcResponse);
   }

}

服务端一切正常

服务端读取和对客户端请求超时重发同一个请求包的处理

@Override
    protected void channelRead0(ChannelHandlerContext ctx, RpcRequest msg) throws Exception {
        try {
            /**
             * 心跳包 只 作为 检测包,不做处理
             */
            if (msg.getHeartBeat()) {
                log.trace("receive hearBeatPackage from customer...");
                return;
            }
            log.info("server has received request: {}", msg);

            // 到了这一步,如果请求包在上一次已经被 服务器成功执行,接下来要做幂等性处理,也就是客户端设置超时重试处理
            /**
             * 这里要防止重试
             * 分为两种情况
             * 1. 如果是 客户端发送给服务端 途中出现问题,请求包之前 服务器未获取到,也就是 唯一请求id号 没有重复
             * 2. 如果是 服务端发回客户端途中出现问题,导致客户端触发 超时重试,这时服务端会 接收 重试请求包,也就是有 重复请求id号
             */
            // 请求id 为第一次请求 id
            Object result = null;
            if (timeoutRetryRequestIdSet.add(msg.getRequestId())) {
                result = requestHandler.handler(msg);
                resMap.put(msg.getRequestId(), result);
            //请求id 为第二次或以上请求
            } else {
                result = resMap.get(msg.getRequestId());
            }
            // 生成 校验码,客户端收到后 会 对 数据包 进行校验
            if (ctx.channel().isActive() && ctx.channel().isWritable()) {
                /**
                 * 这里要分两种情况:
                 * 1. 当数据无返回值时,保证 checkCode 与 result 可以检验,客户端 也要判断 result 为 null 时 checkCode 是否也为 null,才能认为非他人修改
                 * 2. 当数据有返回值时,校验 checkCode 与 result 的 md5 码 是否相同
                 */
                String checkCode = "";
                // 这里做了 当 data为 null checkCode 为 null,checkCode可作为 客户端的判断 返回值 依据
                if(result != null) {
                    checkCode = new String(DigestUtils.md5(result.toString().getBytes("UTF-8")));
                } else {
                    checkCode = null;
                }

                RpcResponse rpcResponse = RpcResponse.success(result, msg.getRequestId(),checkCode);
                ChannelFuture future = ctx.writeAndFlush(rpcResponse);

                /**
                 * 及时清除不用的请求 id
                 * 大于 1000 条请求id 后将请求
                 * 保存此时 服务接收的请求 id
                 * 考虑多线程中 对其他 线程刚添加的请求id 进行清除的影响
                 */
                if (timeoutRetryRequestIdSet.size() >= 1000) {
                    synchronized (this) {
                        if (timeoutRetryRequestIdSet.size() >= 1000) {
                            timeoutRetryRequestIdSet.clear();
                            resMap.clear();
                            timeoutRetryRequestIdSet.add(msg.getRequestId());
                            resMap.put(msg.getRequestId(), result);
                        }
                    }
                }


            } else {
                log.error("channel is not writable");
            }
            /**
             * 1. 通道关闭后,对于 心跳包 将不可用
             * 2. 由于客户端 使用了 ChannelProvider 来 缓存 channel,这里关闭后,无法 发挥 channel 缓存的作用
             */
            //future.addListener(ChannelFutureListener.CLOSE);
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("error occurred while invoking!,info: ", cause);
        ctx.close();
    }

客户端时请求执行远程方法100次的,因为异常到13次就中断了,这样业务损失太高了,怎么做呀

...全文
239 1 打赏 收藏 转发到动态 举报
写回复
用AI写文章
1 条回复
切换为时间正序
请发表友善的回复…
发表回复
嗝屁小孩纸 2022-10-17
  • 打赏
  • 举报
回复
我想到一种方策了,因为我是一个客户端对一条通道,抛出异常得原因就是我未complete之前,因为服务还在执行,还没给客户端,但就先去getting而且超时了,就导致在拿到服务端之后complete异常了,这个异常影响到通道导致通道关闭,通道关闭后客户端当然没法继续调用远程服务了,所以解决通道重新打开即可?是这个意思嘛?

6

社区成员

发帖
与我相关
我的任务
社区描述
RPC学习分享社区: 欢迎大家加入本社区,一起学习和RPC相关的知识,欢迎提出问题,一起交流分享~~ 内容包括但不限于: 网络通信,序列化框架,代理框架,负载均衡,注册中心等。
rpcjava 个人社区
社区管理员
  • trigger333
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

欢迎大家加入RPC知识分享问答社区,我们希望逐步的充实社区内容,希望大家可以在这里了解RPC的发展历史,现有框架,具体实现以及性能调优等等!

希望大家积极投递博文~  积极问答~

试试用AI创作助手写篇文章吧