1、业务场景,合同SCF服务调用电签平台SCF服务时,出现超时错误,如下:
从SCF的帮助文档上我们可以找到相应的解决方案,如下:
最开始我们可能都会认为应该是服务端处理时间过长导致客户端等待超时,于是去服务管理平台修改receiveTimeout的值,但是我们已经将receiveTimeout修改为90秒,仍然出现超时错误,查看服务管理平台也发现服务方好像并未收到客户端的请求,于是我们觉得应该是客户端的问题,通过查看代码发现客户端进行RPC调用时会传递一个字节数组,该数组储存合同服务的合同信息,经查看合同文件的大小在700K左右。
于是猜想是否是因为该数组储存数据过大导致数据其实并未从客户端发送出去,SCF 客户端有一个maxPackageSize用于设置客户端每次发送请求包的最大大小,默认值为102400,也就是默认最大为100K。
我们将maxPackageSize设置为4096000(4000K)后,客户端超时问题解决,看来确实是因为该值设置过小导致客户端出现超时错误。按照我们的猜想如果是参数出现问题,那么请求的时候应该会直接报参数校验错误才对,为何会出现超时错误呢?下面我们从SCF客户端的源码进行分析:
2、SCF 客户端源码解析:
(1)我们在项目如果需要进行RPC调用会使用ProxyFactory创建一个代理对象,所有的RPC请求都由该代理对象发出:
(2)当代理对象发送实际的RPC请求时实际是将请求封装为一个TransmitterTask放入阻塞队列中,如果是异步调用当前线程直接返回,否则如果是同步调用将会阻塞等待服务端的响应(我们使用的是同步调用):
(3)之后会有其他的线程不断从阻塞队列中拿出任务执行:
sendTask其实是一个类。。其是Transmitter的一个内部类:
send方法内部将使用CSocket发送数据:
上面的异常信息截不全,我再补个图:
等等。。为啥我们在服务管理平台配置的属性是maxPackageSize,而代码里面判断的却是sendBuffer的值呢??在CSocket的构造方法中我们可以看到答案:
而当发送数据内部抛出异常时,异常会被捕获,并打印一条日志,其他啥事没有~
(4)如果是同步调用,线程将请求包装成Task丢到阻塞队列后会等待服务端响应数据的返回:
等待服务端的数据返回在receive方法中,我们在控制台查询到的错误日志也是在这里面打印的:
throw new TimeoutException("ServiceName:[" + this.getServiceName() + "],ServiceIP:[" + this.getServiceIP() + "],Receive data timeout or error!timeout:" + timeout + "ms,queue length:" + queueLen);
在Event.waitOne里面使用了CountDownLatch阻塞等待指定的时间,如果在等待过程中接受到服务端的响应将会被唤醒,否则将一直等待到超时:
3、总结:由于客户端在发送RPC请求时底层其实是异步的(当前线程将RPC请求包装成一个任务放到阻塞队列中,等待其他线程获取任务来执行),之后会使用CountDownLatch进行阻塞等待服务端的响应,当到达我们设置的receiveTimeout时间后抛出异常。因为其他线程获取阻塞队列中的任务来执行时抛出了异常,所以请求实际并未到达服务端,此时不管我们将客户端的超时时间设置多大都没用,因为不可能收到服务端的响应,阻塞的线程也就永远不会被主动唤醒。