在 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 框架中可能的调用链分析:
整体调用链概述
- 代理调用触发:客户端通过代理对象调用服务方法,代理对象会将调用转换为
SofaRequest
对象。 - 客户端调用执行:
ClientProxyInvoker
负责执行客户端调用,将SofaRequest
发送给集群。 - 集群选择节点:
Cluster
负责选择合适的服务节点进行调用。 - 异步请求发送:请求通过
AbstractHttp2ClientTransport
或其他传输层发送到服务端。 - 服务端处理请求:服务端接收到请求并处理,返回
SofaResponse
。 - 客户端接收响应:客户端传输层接收到响应后,调用
CallbackInvokeClientHandler
处理响应。 - 回调方法执行:
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. 客户端接收响应
客户端传输层接收到响应后,会调用 CallbackInvokeClientHandler
的 doOnResponse
或 doOnException
方法处理响应。
// 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 调用。这种异步调用方式可以提高系统的并发处理能力,避免线程阻塞,适用于对响应时间要求不高的场景。