Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

增加Server Timeout支持,以避免不必要的response造成带宽占用 #83

Closed
qinzuoyan opened this issue Apr 1, 2016 · 5 comments

Comments

@qinzuoyan
Copy link
Collaborator

问题描述

当RequestTimeout之后,实际上对应Socket没有关闭,超时后数据还是会被接收,这在传输大数据块时,会占用较大带宽。

原因分析

这个问题存在的原因在于:

  • Socket连接是会持续保持的,只要不出现传输上的错误或者keep_alive_time超时,socket是不会被关闭的。这样的好处是socket可以最大限度重用,避免重新建立连接的代价;另外socket是复用的,即不同的request在目标地址相同的情况下会共用一个socket通道,如果因为一个request超时就关闭socket是不合理的。
  • 请求超时这不是传输上的错误,因此socket不会关闭,此时关键问题是server端并不知道client端已经超时了,所以依然会正常处理request并把结果发送给client端,造成带宽占用。

解决方案

考虑如下:

  • 在client发送request给server端时,携带一个server_timeout,在server端收到request时记录receive_time,在请求处理完成后通过(current_time - receive_time)计算实际处理时间process_time,如果process_time > server_timeout,则可以推断此时client已经超时了,就不用发response消息了
  • server_timeout是小于client_timeout的,可以通过client_timeout - (client_send_time - client_begin_time)计算得到
  • 可以进一步优化,在request的发送、接收、开始处理、处理完成、发送response的任何时间点,都可以估算剩余时间,如果剩余时间耗尽,在任何时间点都可以提前终止处理,因为client已经超时,再处理也没有意义了。这可以减少不必要的request发送、request在server端的处理、response发送。
  • 另外,这个问题其实与cancel是有关联的:google::protobuf::RpcController其实有一个StartCancel()接口,可以cancel掉一个request使其提前终止,其目的有共同之处。这个功能目前还没有实现,在TODO list中。

实现

参见pull request #84
但是还有改进的地方:

  • 目前client直接将total_timeout作为server_timeout传给server端,实际上,request在client端的sending buffer中的排队时间没有考虑进来,尤其是网络繁忙的时候排队时间可能会很长,这样server_timeout与实际剩余时间的差别就比较大,仍然会有不必要的response。在RpcClientStream::on_sending()中留了一个TODO就是这个意思。
@bluebore
Copy link
Collaborator

bluebore commented Apr 1, 2016

这个issue解决了不必要的response,是否能再加个功能,避免不必要的request,即:
用户的service可能是同步实现,因为过载而导致堵塞。server端收到request后,要排队调用用户的service,在真正调用用户service前,请求可能已经超时,这时候继续调用用户的service,产生response,再丢弃,其实也是很浪费的。

@yuandong1222
Copy link
Contributor

GREAT WORK!

@qinzuoyan
Copy link
Collaborator Author

@bluebore ,我明白你的意思,我要说几点:

  • server端其实是没有队列的,我们的server端线程模型是work线程既负责处理网络IO也负责处理服务,因此是没有显式的等待队列的;如果有work线程空闲,它就会从socket中读取request数据(通过epoll的方式),然后立即处理它;
  • 但并不是说server端request就不会等待,实际上,work线程从socket中有可能一次性读取到多个request,这取决于epoll一次返回多少数据,有可能多个request合并成一个数据块返回。如果一次获取了多个request,目前做发是逐个串行执行,参见RpcServerMessageStream::on_read_some()函数:
... ...
        // process messages
        while (!is_closed() && !received_messages.empty())
        {
            on_received(received_messages.front());
            received_messages.pop_front();
        }
... ...
  • 因此从work线程读取到request到真正开始执行request之间确实是可能有一段时间延迟的,这个延迟就是在其之前执行的同一批request的执行时间之和。
  • 目前RpcControllerImpl中针对server端记录了这三个时间点:_request_received_time、_start_process_time、_finish_process_time。通过计算 server_wait_time = _start_process_time - _request_received_time 可以得到在server端的等待时间。如果server_wait_time >= server_timeout,那么就可以放弃执行了。
  • 另外在client端,从开始计时到开始发送之间也可能在pending buffer中排队,这段时间记为client_wait_time,那么client传递给server的server_timeout实际上要减去这段时间才更准确,即server_timeout = client_timeout - client_wait_time。现在没有减去client_wait_time,是因为server_timeout是存放在RpcMeta中,而RpcMeta在放入pending buffer之前就需要序列化到buffer中,在后面发送时很难修改。除非我们的序列化操作推迟到发送时再做。

@liangjianqun
Copy link

赞这个主题!
可以考虑把用户最初掉用rpc的start_time和time_out附带上,在真正发送rpc时判断一下超时;
对端收到请求时记录一下receive_time,处理过程中或者response时都可判断一下是否已经超时

@qinzuoyan
Copy link
Collaborator Author

参见#86 ,目前已经考虑了server_wait_time,现在就差client_wait_time需要考虑了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants