5.源码分析---SOFARPC调用服务
我们这一次来接着上一篇文章《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();
}
}
这个方法主要做了如下几件事:
- 设置上下文参数
- 从缓存中得到服务暴露时设置的invoker
- 为request设置method参数
- 调用doInvoke返回response
- 将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调用服务的更多相关文章
- 7.源码分析---SOFARPC是如何实现故障剔除的?
我在服务端引用那篇文章里面分析到,服务端在引用的时候会去获取服务端可用的服务,并进行心跳,维护一个可用的集合. 所以我们从客户端初始化这部分说起. 服务连接的维护 客户端初始化的时候会调用cluste ...
- 9.源码分析---SOFARPC是如何实现故障剔除的?
SOFARPC源码解析系列: 1. 源码分析---SOFARPC可扩展的机制SPI 2. 源码分析---SOFARPC客户端服务引用 3. 源码分析---SOFARPC客户端服务调用 4. 源码分析- ...
- 11.源码分析---SOFARPC数据透传是实现的?
先把栗子放上,让大家方便测试用: Service端 public static void main(String[] args) { ServerConfig serverConfig = new S ...
- 10.源码分析---SOFARPC内置链路追踪SOFATRACER是怎么做的?
SOFARPC源码解析系列: 1. 源码分析---SOFARPC可扩展的机制SPI 2. 源码分析---SOFARPC客户端服务引用 3. 源码分析---SOFARPC客户端服务调用 4. 源码分析- ...
- 4. 源码分析---SOFARPC服务端暴露
服务端的示例 我们首先贴上我们的服务端的示例: public static void main(String[] args) { ServerConfig serverConfig = new Ser ...
- angular源码分析:$compile服务——directive他妈
一.directive的注册 1.我们知道,我们可以通过类似下面的代码定义一个指令(directive). var myModule = angular.module(...); myModule.d ...
- Fresco 源码分析(三) Fresco服务端处理(1) ImagePipeline为何物
4.3 服务端的处理 备注: 因为是分析,而不是设计,所以很多知识我们类似于插叙的方式叙述,就是用到了哪个知识点,我们再提及相关的知识点,如果分析到了最后,我想想是不是应该将这个架构按照设计的方式,重 ...
- Dubbo2.7源码分析-如何发布服务
Dubbo的服务发布逻辑是比较复杂的,我还是以Dubbo自带的示例讲解,这样更方便和容易理解. Provider配置如下: <?xml version="1.0" encod ...
- dubbo源码分析02:服务引用
一.何时创建服务引用 引用官方文档的原话,如果将Dubbo托管在Spring-IOC容器下,Dubbo服务引用的时机有两个,第一个是在Spring容器调用ReferenceBean的afterProp ...
随机推荐
- JS中闭包的介绍
闭包的概念 闭包就是能够读取其他函数内部变量的函数. 一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascri ...
- hdoj2037 贪心算法——今年暑假不AC
所谓“贪心算法”是指:在对问题求解时,总是作出在当前看来是最好的选择.也就是说,不从整体上加以考虑,它所作出的仅仅是在某种意义上的局部最优解(是否是全局最优,需要证明). 经典问题:时间序列问题 ...
- POJ 1485:Fast Food(dp)&& 面试题
题目链接 题意 给出 n 个餐厅,m 个停车场,现在要将 n 个餐厅中的 m 个变成停车场,使得每个餐厅到最近的停车场的距离之和最短,输出哪个餐厅变成停车场和它服务哪些餐厅,还有最短距离之和. 思路 ...
- ORACLE常用函数的使用方法
ORACLE常用函数的使用方法 1. 字符串函数 (1) length(); 获取字符长度SELECT LENGTH('中国') FROM PLATFORM_METAINFO_TABLES WHERE ...
- fiddler无法抓取chrome浏览器请求的解决方案之关闭代理软件
最近安装fiddler后,按照通用设置后依然无法抓取到chrome的请求,经检查,我本地的chrome安装了代理管理的插件Switchy,无论选择直接连接还是选择使用代理连接,插件都会屏蔽fiddle ...
- 删除git中缓存的用户名和密码
我们使用Git命令去clone Gitlab仓库的代码时,第一次弹框提示输入账号密码的时候输错了,然后后面就一直拒绝,不再重复提示输入账号密码,怎么破? git报错信息 运行一下命令缓存输入的用户名和 ...
- 关于String重写的hashcode的代码分析
public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = valu ...
- 剑指offer第二版-4.二维数组中的查找
面试题4:二维数组中的查找 题目要求: 一个二维数组中,每一行从左到右递增,每一列从上到下递增.输入一个整数,判断数组中是否含有该整数 /** * @since 2019年2月13日 下午5:08:5 ...
- NOI 2011 兔农 题解
事先声明,本博客代码主要模仿accepoc,且仅针对一般如本博主一样的蒟蒻. 这道题不得不说数据良心,给了75分的水分,但剩下25分真心很难得到,因此我们就来讲一讲这剩下的25分. 首先,有数据可知他 ...
- 视频4K技术的解读
前几年4K技术就已经有人提及,今年更是成了一个非常热门的词汇,而且4K技术已经普遍应用于各类终端,如电视机.机顶盒.手机等.那么如何来理解4K这个东东呢?今天博主就谈谈自己对4K技术的认识. 博主认为 ...