我们这一次来接着上一篇文章《4. 源码分析---SOFARPC服务端暴露》讲一下服务暴露之后被客户端调用之后服务端是怎么返回数据的。

示例我们还是和上篇文章一样使用一样的bolt协议来讲:

    public static void main(String[] args) {
ServerConfig serverConfig = new ServerConfig()
.setProtocol("bolt") // 设置一个协议,默认bolt
.setPort(12200) // 设置一个端口,默认12200
.setDaemon(false); // 非守护线程 ProviderConfig<HelloService> providerConfig = new ProviderConfig<HelloService>()
.setInterfaceId(HelloService.class.getName()) // 指定接口
.setRef(new HelloServiceImpl()) // 指定实现
.setServer(serverConfig); // 指定服务端 providerConfig.export(); // 发布服务
}

在Bolt协议下面,当服务端被调用的时候一个服务的流程如下所示:

BoltServerProcessor->FilterChain->ProviderExceptionFilter->FilterInvoker->RpcServiceContextFilter->FilterInvoker->ProviderBaggageFilter->FilterInvoker->ProviderTracerFilter->ProviderInvoker

BoltServerProcessor#handleRequest

@Override
public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, SofaRequest request) {
// RPC内置上下文
RpcInternalContext context = RpcInternalContext.getContext();
context.setProviderSide(true); String appName = request.getTargetAppName();
if (appName == null) {
// 默认全局appName
appName = (String) RpcRuntimeContext.get(RpcRuntimeContext.KEY_APPNAME);
} // 是否链路异步化中
boolean isAsyncChain = false;
try { // 这个 try-finally 为了保证Context一定被清理
processingCount.incrementAndGet(); // 统计值加1 context.setRemoteAddress(bizCtx.getRemoteHost(), bizCtx.getRemotePort()); // 远程地址
context.setAttachment(RpcConstants.HIDDEN_KEY_ASYNC_CONTEXT, asyncCtx); // 远程返回的通道 if (RpcInternalContext.isAttachmentEnable()) {
InvokeContext boltInvokeCtx = bizCtx.getInvokeContext();
if (boltInvokeCtx != null) {
putToContextIfNotNull(boltInvokeCtx, InvokeContext.BOLT_PROCESS_WAIT_TIME,
context, RpcConstants.INTERNAL_KEY_PROCESS_WAIT_TIME); // rpc线程池等待时间 Long
}
}
if (EventBus.isEnable(ServerReceiveEvent.class)) {
EventBus.post(new ServerReceiveEvent(request));
} // 开始处理
SofaResponse response = null; // 响应,用于返回
Throwable throwable = null; // 异常,用于记录
ProviderConfig providerConfig = null;
String serviceName = request.getTargetServiceUniqueName(); try { // 这个try-catch 保证一定有Response
invoke:
{
if (!boltServer.isStarted()) { // 服务端已关闭
throwable = new SofaRpcException(RpcErrorType.SERVER_CLOSED, LogCodes.getLog(
LogCodes.WARN_PROVIDER_STOPPED, SystemInfo.getLocalHost() + ":" +
boltServer.serverConfig.getPort()));
response = MessageBuilder.buildSofaErrorResponse(throwable.getMessage());
break invoke;
}
if (bizCtx.isRequestTimeout()) { // 加上丢弃超时的请求的逻辑
throwable = clientTimeoutWhenReceiveRequest(appName, serviceName, bizCtx.getRemoteAddress());
break invoke;
}
// 查找服务
//在server.registerProcessor方法中设置 ProviderProxyInvoker
Invoker invoker = boltServer.findInvoker(serviceName);
if (invoker == null) {
throwable = cannotFoundService(appName, serviceName);
response = MessageBuilder.buildSofaErrorResponse(throwable.getMessage());
break invoke;
}
if (invoker instanceof ProviderProxyInvoker) {
providerConfig = ((ProviderProxyInvoker) invoker).getProviderConfig();
// 找到服务后,打印服务的appName
appName = providerConfig != null ? providerConfig.getAppName() : null;
}
// 查找方法
String methodName = request.getMethodName();
//在server.registerProcessor方法中设置
Method serviceMethod = ReflectCache.getOverloadMethodCache(serviceName, methodName,
request.getMethodArgSigs());
if (serviceMethod == null) {
throwable = cannotFoundServiceMethod(appName, methodName, serviceName);
response = MessageBuilder.buildSofaErrorResponse(throwable.getMessage());
break invoke;
} else {
request.setMethod(serviceMethod);
} // 真正调用
response = doInvoke(serviceName, invoker, request); if (bizCtx.isRequestTimeout()) { // 加上丢弃超时的响应的逻辑
throwable = clientTimeoutWhenSendResponse(appName, serviceName, bizCtx.getRemoteAddress());
break invoke;
}
}
} catch (Exception e) {
// 服务端异常,不管是啥异常
LOGGER.errorWithApp(appName, "Server Processor Error!", e);
throwable = e;
response = MessageBuilder.buildSofaErrorResponse(e.getMessage());
} // Response不为空,代表需要返回给客户端
if (response != null) {
RpcInvokeContext invokeContext = RpcInvokeContext.peekContext();
isAsyncChain = CommonUtils.isTrue(invokeContext != null ?
(Boolean) invokeContext.remove(RemotingConstants.INVOKE_CTX_IS_ASYNC_CHAIN) : null);
// 如果是服务端异步代理模式,特殊处理,因为该模式是在业务代码自主异步返回的
if (!isAsyncChain) {
// 其它正常请求
try { // 这个try-catch 保证一定要记录tracer
asyncCtx.sendResponse(response);
} finally {
if (EventBus.isEnable(ServerSendEvent.class)) {
EventBus.post(new ServerSendEvent(request, response, throwable));
}
}
}
}
} catch (Throwable e) {
// 可能有返回时的异常
if (LOGGER.isErrorEnabled(appName)) {
LOGGER.errorWithApp(appName, e.getMessage(), e);
}
} finally {
processingCount.decrementAndGet();
if (!isAsyncChain) {
if (EventBus.isEnable(ServerEndHandleEvent.class)) {
EventBus.post(new ServerEndHandleEvent());
}
}
RpcInvokeContext.removeContext();
RpcInternalContext.removeAllContext();
}
}

这个方法主要做了如下几件事:

  1. 设置上下文参数
  2. 从缓存中得到服务暴露时设置的invoker
  3. 为request设置method参数
  4. 调用doInvoke返回response
  5. 将response返回给客户端

BoltServerProcessor#doInvoke

我们直接进入到doInvoke方法中,看是如何生成response对象的。

private SofaResponse doInvoke(String serviceName, Invoker invoker, SofaRequest request) throws SofaRpcException {
// 开始调用,先记下当前的ClassLoader
ClassLoader rpcCl = Thread.currentThread().getContextClassLoader();
try {
// 切换线程的ClassLoader到 服务 自己的ClassLoader
ClassLoader serviceCl = ReflectCache.getServiceClassLoader(serviceName);
Thread.currentThread().setContextClassLoader(serviceCl);
return invoker.invoke(request);
} finally {
Thread.currentThread().setContextClassLoader(rpcCl);
}
}

这里主要是为了获取缓存里面加载被暴露服务的类加载器,这样可以防止不同的类加载器之间一个类被加载多次。

然后调用过滤器链,最后进入到ProviderInvoker中

ProviderInvoker#invoke

@Override
public SofaResponse invoke(SofaRequest request) throws SofaRpcException {
SofaResponse sofaResponse = new SofaResponse();
long startTime = RpcRuntimeContext.now();
try {
// 反射 真正调用业务代码
Method method = request.getMethod();
if (method == null) {
throw new SofaRpcException(RpcErrorType.SERVER_FILTER, "Need decode method first!");
}
Object result = method.invoke(providerConfig.getRef(), request.getMethodArgs()); sofaResponse.setAppResponse(result);
} catch (IllegalArgumentException e) { // 非法参数,可能是实现类和接口类不对应)
sofaResponse.setErrorMsg(e.getMessage());
} catch (IllegalAccessException e) { // 如果此 Method 对象强制执行 Java 语言访问控制,并且底层方法是不可访问的
sofaResponse.setErrorMsg(e.getMessage());
} catch (InvocationTargetException e) { // 业务代码抛出异常
cutCause(e.getCause());
sofaResponse.setAppResponse(e.getCause());
} finally {
if (RpcInternalContext.isAttachmentEnable()) {
long endTime = RpcRuntimeContext.now();
RpcInternalContext.getContext().setAttachment(RpcConstants.INTERNAL_KEY_IMPL_ELAPSE,
endTime - startTime);
}
} return sofaResponse;
}

到最后我们发现,服务端会通过反射调用被暴露服务的方法,封装成Response类返回。

我们再次回到BoltServerProcessor#handleRequest方法中

....//忽略其他内容
// Response不为空,代表需要返回给客户端
if (response != null) {
RpcInvokeContext invokeContext = RpcInvokeContext.peekContext();
isAsyncChain = CommonUtils.isTrue(invokeContext != null ?
(Boolean) invokeContext.remove(RemotingConstants.INVOKE_CTX_IS_ASYNC_CHAIN) : null);
// 如果是服务端异步代理模式,特殊处理,因为该模式是在业务代码自主异步返回的
if (!isAsyncChain) {
// 其它正常请求
try { // 这个try-catch 保证一定要记录tracer
asyncCtx.sendResponse(response);
} finally {
if (EventBus.isEnable(ServerSendEvent.class)) {
EventBus.post(new ServerSendEvent(request, response, throwable));
}
}
}
}
....//忽略其他内容

最后我们的response实例会使用netty传给客户端。

5.源码分析---SOFARPC调用服务的更多相关文章

  1. 7.源码分析---SOFARPC是如何实现故障剔除的?

    我在服务端引用那篇文章里面分析到,服务端在引用的时候会去获取服务端可用的服务,并进行心跳,维护一个可用的集合. 所以我们从客户端初始化这部分说起. 服务连接的维护 客户端初始化的时候会调用cluste ...

  2. 9.源码分析---SOFARPC是如何实现故障剔除的?

    SOFARPC源码解析系列: 1. 源码分析---SOFARPC可扩展的机制SPI 2. 源码分析---SOFARPC客户端服务引用 3. 源码分析---SOFARPC客户端服务调用 4. 源码分析- ...

  3. 11.源码分析---SOFARPC数据透传是实现的?

    先把栗子放上,让大家方便测试用: Service端 public static void main(String[] args) { ServerConfig serverConfig = new S ...

  4. 10.源码分析---SOFARPC内置链路追踪SOFATRACER是怎么做的?

    SOFARPC源码解析系列: 1. 源码分析---SOFARPC可扩展的机制SPI 2. 源码分析---SOFARPC客户端服务引用 3. 源码分析---SOFARPC客户端服务调用 4. 源码分析- ...

  5. 4. 源码分析---SOFARPC服务端暴露

    服务端的示例 我们首先贴上我们的服务端的示例: public static void main(String[] args) { ServerConfig serverConfig = new Ser ...

  6. angular源码分析:$compile服务——directive他妈

    一.directive的注册 1.我们知道,我们可以通过类似下面的代码定义一个指令(directive). var myModule = angular.module(...); myModule.d ...

  7. Fresco 源码分析(三) Fresco服务端处理(1) ImagePipeline为何物

    4.3 服务端的处理 备注: 因为是分析,而不是设计,所以很多知识我们类似于插叙的方式叙述,就是用到了哪个知识点,我们再提及相关的知识点,如果分析到了最后,我想想是不是应该将这个架构按照设计的方式,重 ...

  8. Dubbo2.7源码分析-如何发布服务

    Dubbo的服务发布逻辑是比较复杂的,我还是以Dubbo自带的示例讲解,这样更方便和容易理解. Provider配置如下: <?xml version="1.0" encod ...

  9. dubbo源码分析02:服务引用

    一.何时创建服务引用 引用官方文档的原话,如果将Dubbo托管在Spring-IOC容器下,Dubbo服务引用的时机有两个,第一个是在Spring容器调用ReferenceBean的afterProp ...

随机推荐

  1. Python连载20-偏函数&zip函数&enumerate函数

    一. 偏函数 二. #先举个例子 #把字符串转换为十进制数字 int(') #help(int),int函数中有一个参数base代表把它转换某个进制的数字 #把八进制的字符串转换为十进制 eight ...

  2. 使用Minikube部署本地Kubernetes集群(二十八)

    前言 使用Minikube部署本地k8s集群相对比较简单,非常推荐将其用于本地k8s开发环境,唯一麻烦点的仅仅是网络问题. 在本篇教程中,我们使用了国内的镜像来完成本地k8s集群的搭建.如果搭建过程中 ...

  3. Codeforces 755A:PolandBall and Hypothesis(暴力)

    http://codeforces.com/problemset/problem/755/A 题意:给出一个n,让你找一个m使得n*m+1不是素数. 思路:暴力枚举m判断即可. #include &l ...

  4. js查询checkbox已选择的值

    $("input[id^=ck]").each(function(index,e){ if($(e).is(":checked")) { userArray.p ...

  5. 视图系统CBV 和 response

     CBV和FBV FBV(function based view ) CBV(class based view) 1. CBV的定义 # 增加出版社 CBV from django.views imp ...

  6. ZigBee按键查询实践

    按键查询 即硬件上电后,程序开始运行,当检测到按键按下,触发按键语句,执行按键触发的事件: 玩单片机需要清楚两件东西,第一个是单片机的电路图,另一个则是单片机的寄存器: CC2530是51的升级版,我 ...

  7. Spring Cloud Alibaba | Sentinel: 分布式系统的流量防卫兵初探

    目录 Spring Cloud Alibaba | Sentinel: 分布式系统的流量防卫兵初探 1. Sentinel 是什么? 2. Sentinel 的特征: 3. Sentinel 的开源生 ...

  8. MyBatis 使用枚举或其他对象

    From<Mybatis从入门到精通> 1.笔记: <!-- 6.3 使用枚举或者其他对象 6.3.1 使用MyBatis提供的枚举处理器 不懂: 因为枚举除了本身的字面值外,还可以 ...

  9. Linux 文件编程、时间编程基本函数

    文件编程 文件描述符 fd --->>>数字(文件的身份证,代表文件身份),通过 fd 可找到正在操作或需要打开的文件. 基本函数操作: 1)打开/创建文件 int open (co ...

  10. Unity 3d 脚本常用事件

    1.学习笔记,每天晚上看看unity 3d 的教程 学点东西,先上一张图 这是 系统中脚本执行的顺序图 Awake()在MonoBehavior创建后就立刻调用,在脚本实例的整个生命周期中,Awake ...