SOFA RPC是如何实现callback回调的

在 SOFARPC 中,Callback 调用是一种异步调用方式,允许客户端在发送请求后继续执行其他任务,当服务端返回结果时,通过回调函数处理响应。以下详细介绍 SOFARPC 实现 Callback 调用的步骤和原理:

1. 定义回调接口

SOFARPC 提供了SofaResponseCallback接口,用于处理服务端的响应。该接口包含三个方法,分别处理正常响应、业务异常和框架异常:

public interface SofaResponseCallback<T> {
    void onAppResponse(Object appResponse, String methodName, RequestBase request);
    void onAppException(Throwable throwable, String methodName, RequestBase request);
    void onSofaException(SofaRpcException sofaException, String methodName, RequestBase request);
}

2. 配置消费者

在消费者端,需要配置ConsumerConfig,设置调用类型为CALLBACK,并提供回调函数。可以在接口级别、方法级别或调用级别设置回调函数。

接口级别设置回调
ConsumerConfig<HelloService> consumerConfig = new ConsumerConfig<HelloService>()
    .setApplication(applicationConfig)
    .setInterfaceId(HelloService.class.getName())
    .setInvokeType(RpcConstants.INVOKER_TYPE_CALLBACK)
    .setOnReturn(new SofaResponseCallback() {
        @Override
        public void onAppResponse(Object appResponse, String methodName, RequestBase request) {
            LOGGER.info("Interface get result: {}", appResponse);
        }

        @Override
        public void onAppException(Throwable throwable, String methodName, RequestBase request) {
            LOGGER.info("Interface get app exception: {}", throwable);
        }

        @Override
        public void onSofaException(SofaRpcException sofaException, String methodName,
                                    RequestBase request) {
            LOGGER.info("Interface get sofa exception: {}", sofaException);
        }
    })
    .setDirectUrl("bolt://127.0.0.1:22222?appName=future-server");
HelloService helloService = consumerConfig.refer();
方法级别设置回调
MethodConfig methodConfig = new MethodConfig();
methodConfig.setName("sayHello")
    .setInvokeType(RpcConstants.INVOKER_TYPE_CALLBACK)
    .setOnReturn(new SofaResponseCallback() {
        @Override
        public void onAppResponse(Object appResponse, String methodName, RequestBase request) {
            LOGGER.info("Method get result: {}", appResponse);
        }

        @Override
        public void onAppException(Throwable throwable, String methodName, RequestBase request) {
            LOGGER.info("Method get app exception: {}", throwable);
        }

        @Override
        public void onSofaException(SofaRpcException sofaException, String methodName,
                                    RequestBase request) {
            LOGGER.info("Method get sofa exception: {}", sofaException);
        }
    });

ConsumerConfig<HelloService> consumerConfig = new ConsumerConfig<HelloService>()
    .setApplication(applicationConfig)
    .setInterfaceId(HelloService.class.getName())
    .setMethods(Collections.singletonList(methodConfig))
    .setDirectUrl("bolt://127.0.0.1:22222?appName=future-server");
HelloService helloService = consumerConfig.refer();
调用级别设置回调
RpcInvokeContext.getContext().setResponseCallback(
    new SofaResponseCallback() {
        @Override
        public void onAppResponse(Object appResponse, String methodName, RequestBase request) {
            LOGGER.info("Invoke get result: {}", appResponse);
        }

        @Override
        public void onAppException(Throwable throwable, String methodName, RequestBase request) {
            LOGGER.info("Invoke get app exception: {}", throwable);
        }

        @Override
        public void onSofaException(SofaRpcException sofaException, String methodName,
                                    RequestBase request) {
            LOGGER.info("Invoke get sofa exception: {}", sofaException);
        }
    }
);

String s = helloService.sayHello("xxx", 22);

3. 发起异步调用

当消费者调用服务时,SOFARPC 会根据配置的调用类型进行处理。对于 Callback 调用,会将请求异步发送给服务端,并立即返回一个空响应,不会阻塞当前线程:

else if (RpcConstants.INVOKER_TYPE_CALLBACK.equals(invokeType)) {
    // 调用级别回调监听器
    SofaResponseCallback sofaResponseCallback = request.getSofaResponseCallback();
    if (sofaResponseCallback == null) {
        SofaResponseCallback methodResponseCallback = consumerConfig
            .getMethodOnreturn(request.getMethodName());
        if (methodResponseCallback != null) { // 方法的Callback
            request.setSofaResponseCallback(methodResponseCallback);
        }
    }
    // 记录发送开始时间
    context.setAttachment(RpcConstants.INTERNAL_KEY_CLIENT_SEND_TIME, RpcRuntimeContext.now());
    // 开始调用
    transport.asyncSend(request, timeout);
    response = buildEmptyResponse(request);
}
3.1 AbstractHttp2ClientTransport#doSend

它负责将 SofaRequest 对象转换为 HTTP/2 请求并发送出去。不过你提供的代码片段里并没有 doSend 方法的具体实现

protected void doSend(SofaRequest request, AbstractHttpClientHandler callback, int timeoutMillis) {
     AbstractByteBuf data = null;
        try {
            // 序列化
            byte serializeType = request.getSerializeType();
            Serializer serializer = SerializerFactory.getSerializer(serializeType);
            data = serializer.encode(request, null);
            request.setData(data);
            // 记录请求序列化大小 不是很准,没有记录HTTP头
            RpcInternalContext.getContext().setAttachment(RpcConstants.INTERNAL_KEY_REQ_SIZE, data.readableBytes());

            // 转换请求
            FullHttpRequest httpRequest = convertToHttpRequest(request);

            // 发送请求 并绑定requestid和channel关系,用于找到对应的回调接口
            final int requestId = sendHttpRequest(httpRequest, callback);

            if (request.isAsync()) {
                TIMEOUT_TIMER.newTimeout(new TimerTask() {
                    @Override
                    public void run(Timeout timeout) throws Exception {
                        Map.Entry<ChannelFuture, AbstractHttpClientHandler> entry = responseChannelHandler
                            .removePromise(requestId);
                        if (entry != null) {
                            ClientHandler handler = entry.getValue();
                            Exception e = timeoutException(request, timeoutMills, null);
                            handler.onException(e);
                        }
                    }

                }, timeoutMills, TimeUnit.MILLISECONDS);
            }
        } finally {
            if (data != null) {
                data.release();
            }
        }
}
protected int sendHttpRequest(FullHttpRequest httpRequest, AbstractHttpClientHandler callback) {
    final int requestId = streamId.getAndAdd(2);
    Channel channel = this.channel.channel();
    // 绑定requestId和callback
    responseChannelHandler.put(requestId, channel.write(httpRequest), callback);
    channel.flush();
    return requestId;
}

4. 处理服务端响应

当服务端返回响应时,SOFARPC 会触发相应的回调函数。CallbackInvokeClientHandler类负责处理 Callback 调用的响应:

CallbackInvokeClientHandler 是用于处理异步回调调用的响应处理器。以下是它在 SOFA RPC 框架中可能的调用链分析:

整体调用链概述

  1. 代理调用触发:客户端通过代理对象调用服务方法,代理对象会将调用转换为 SofaRequest 对象。
  2. 客户端调用执行ClientProxyInvoker 负责执行客户端调用,将 SofaRequest 发送给集群。
  3. 集群选择节点Cluster 负责选择合适的服务节点进行调用。
  4. 异步请求发送:请求通过 AbstractHttp2ClientTransport 或其他传输层发送到服务端。
  5. 服务端处理请求:服务端接收到请求并处理,返回 SofaResponse
  6. 客户端接收响应:客户端传输层接收到响应后,调用 CallbackInvokeClientHandler 处理响应。
  7. 回调方法执行CallbackInvokeClientHandler 根据响应结果调用用户提供的回调方法。
@Override
public void doOnResponse(Object result) {
    if (callback == null) {
        return;
    }
    ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
    SofaResponse response = (SofaResponse) result;
    Throwable throwable = null;
    try {
        Thread.currentThread().setContextClassLoader(this.classLoader);
        RpcInternalContext.setContext(context);

        if (EventBus.isEnable(ClientAsyncReceiveEvent.class)) {
            EventBus.post(new ClientAsyncReceiveEvent(consumerConfig, providerInfo,
                request, response, null));
        }

        pickupBaggage(response);

        // do async filter after respond server
        FilterChain chain = consumerConfig.getConsumerBootstrap().getCluster().getFilterChain();
        if (chain != null) {
            chain.onAsyncResponse(consumerConfig, request, response, null);
        }

        recordClientElapseTime();
        if (EventBus.isEnable(ClientEndInvokeEvent.class)) {
            EventBus.post(new ClientEndInvokeEvent(request, response, null));
        }

        decode(response);

        Object appResp = response.getAppResponse();
        if (response.isError()) { // rpc层异常
            SofaRpcException sofaRpcException = new SofaRpcException(
                RpcErrorType.SERVER_UNDECLARED_ERROR, response.getErrorMsg());
            callback.onSofaException(sofaRpcException, request.getMethodName(), request);
        } else if (appResp instanceof Throwable) { // 业务层异常
            throwable = (Throwable) appResp;
            callback.onAppException(throwable, request.getMethodName(), request);
        } else {
            callback.onAppResponse(appResp, request.getMethodName(), request);
        }
    } finally {
        Thread.currentThread().setContextClassLoader(oldCl);
        RpcInvokeContext.removeContext();
        RpcInternalContext.removeAllContext();
    }
}

4.1详细调用链分析

1. 代理调用触发

当客户端调用服务接口的方法时,JDK 代理处理器 JDKInvocationHandler 会拦截该调用,并将其转换为 SofaRequest 对象。

// com.alipay.sofa.rpc.proxy.jdk.JDKInvocationHandler
@Override
public Object invoke(Object proxy, Method method, Object[] paramValues) throws Throwable {
    // ...
    SofaRequest sofaRequest = MessageBuilder.buildSofaRequest(method.getDeclaringClass(), method, paramTypes, paramValues);
    SofaResponse response = proxyInvoker.invoke(sofaRequest);
    // ...
}
2. 客户端调用执行

ClientProxyInvoker 负责执行客户端调用,将 SofaRequest 发送给集群。

// com.alipay.sofa.rpc.client.ClientProxyInvoker
@Override
public SofaResponse invoke(SofaRequest request) throws SofaRpcException {
    // ...
    response = cluster.invoke(request);
    // ...
}
3. 集群选择节点

Cluster 负责选择合适的服务节点进行调用。在异步调用中,会创建 CallbackInvokeClientHandler 来处理响应。

// 假设在Cluster的某个方法中创建CallbackInvokeClientHandler
SofaResponseCallback callback = request.getSofaResponseCallback();
CallbackInvokeClientHandler handler = new CallbackInvokeClientHandler(
    consumerConfig, providerInfo, callback, request, RpcInternalContext.getContext(), Thread.currentThread().getContextClassLoader());
4. 异步请求发送

请求通过 AbstractHttp2ClientTransport 或其他传输层发送到服务端。在发送请求时,会将 CallbackInvokeClientHandler 注册到传输层,以便处理响应。

// 假设在AbstractHttp2ClientTransport的send方法中注册CallbackInvokeClientHandler
responseChannelHandler.put(currentStreamId, null, handler);
5. 服务端处理请求

服务端接收到请求并处理,返回 SofaResponse

6. 客户端接收响应

客户端传输层接收到响应后,会调用 CallbackInvokeClientHandlerdoOnResponsedoOnException 方法处理响应。

// com.alipay.sofa.rpc.transport.http.CallbackInvokeClientHandler
@Override
public void doOnResponse(Object result) {
    // ...
    if (callback == null) {
        return;
    }
    // ...
    if (response.isError()) {
        callback.onSofaException(sofaRpcException, request.getMethodName(), request);
    } else if (appResp instanceof Throwable) {
        callback.onAppException(throwable, request.getMethodName(), request);
    } else {
        callback.onAppResponse(appResp, request.getMethodName(), request);
    }
    // ...
}

@Override
public void doOnException(Throwable e) {
    // ...
    if (callback == null) {
        return;
    }
    // ...
    callback.onSofaException(sofaRpcException, request.getMethodName(), request);
    // ...
}
7. 回调方法执行

CallbackInvokeClientHandler 根据响应结果调用用户提供的回调方法。

4.2调用链总结

JDKInvocationHandler.invoke()
    -> ClientProxyInvoker.invoke()
        -> Cluster.invoke()
            -> AbstractHttp2ClientTransport.send()
                -> 服务端处理请求
                -> 客户端传输层接收响应
                    -> CallbackInvokeClientHandler.doOnResponse() 或 CallbackInvokeClientHandler.doOnException()
                        -> SofaResponseCallback.onAppResponse() 或 SofaResponseCallback.onAppException() 或 SofaResponseCallback.onSofaException()

通过以上调用链,客户端可以实现异步回调调用,并在接收到响应后执行相应的回调方法。

总结

SOFARPC 通过定义回调接口、配置消费者、发起异步调用和处理服务端响应等步骤实现了 Callback 调用。这种异步调用方式可以提高系统的并发处理能力,避免线程阻塞,适用于对响应时间要求不高的场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值