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

http2 / grpc问题 #715

Closed
yjhjstz opened this issue Apr 3, 2019 · 27 comments
Closed

http2 / grpc问题 #715

yjhjstz opened this issue Apr 3, 2019 · 27 comments

Comments

@yjhjstz
Copy link

yjhjstz commented Apr 3, 2019

Describe the bug (描述bug)
[E2004]remote_window_left is not enough, data_size=629370 [R1][E2004]remote_window_left is not enough, data_size=629370
W0403 10:03:43.719316 12510 http2_rpc_protocol.cpp:206] Unknown setting, id=65027 value=1

To Reproduce (复现方法)
http2 client连 grpc服务端,传输大小600KB

Expected behavior (期望行为)

Versions (各种版本)
OS: centos 7
Compiler: gcc4.8
brpc: 0.9.5
protobuf: 3.5

Additional context/screenshots (更多上下文/截图)

传输数据大了,该怎么改?

@yjhjstz
Copy link
Author

yjhjstz commented Apr 3, 2019

@zyearn

@zyearn
Copy link
Member

zyearn commented Apr 3, 2019

原因:目前brpc client的h2setting是和req一起发过去的,这时用的是默认大小,如果超过这个值,就发不出去,那么client的h2setting也发不出去,server端的h2setting也不会回复给client,server端的window size就不能传到client

workaround:

  1. 设置好server端的windowsize
  2. client先发一个小包,让h2的setting包交换好,然后再发你的业务包

@yjhjstz
Copy link
Author

yjhjstz commented Apr 3, 2019

server端是 grpc 服务,怎么改动windowsize?

@zyearn
Copy link
Member

zyearn commented Apr 3, 2019

这个你要看grpc的文档了

@yjhjstz
Copy link
Author

yjhjstz commented Apr 5, 2019

有更优雅的方式吗?

@kaydxh
Copy link

kaydxh commented Jul 15, 2019

server设置了windows大小,好像还是不行

@zyearn
Copy link
Member

zyearn commented Jul 16, 2019

@kaydxh Client还需要先发一个小的包,比如访问下内置服务/health

@supernovaer
Copy link

@kaydxh Client还需要先发一个小的包,比如访问下内置服务/health

试了下,先发个小包再发大包,确实没有windows size的问题了。
但是,如果brpc端配置protocol=h2:grpc,connection_type=single,一段时间没有请求->连接断开之后,直接发大包,就又出现windows size的问题了,必须重新再发一个小包。

可惜server端grpc没有内置的/health服务,小包这个trick还不太好应用。

总的来说,是一个解决办法,但比较麻烦,不够完美。

@supernovaer
Copy link

client h2setting里默认的window_size可以改大吗?
手动把h2_client_stream_window_size、h2_client_connection_window_size两个参数设大也不行

@zyearn
Copy link
Member

zyearn commented Jul 16, 2019

这两个值是可以设置的,确保下server是否收到了,以及正确返回了。然后看一下 ip:port/connections 中client channel的socket页面(有链接可以点),在页面里找到agent_socket_id,然后看一下ip:port/sockets/<agent_socket_id>里的h2 local setting是不是正确

@supernovaer
Copy link

image
在进程启动的时候直接通过gflag配置h2_client_connection_window_size=10485760为10M,h2_client_stream_window_size也是10M,但通过页面 ip:port/sockets/<agent_socket_id>里的h2 local setting,仍然是默认值:conn_window_size=1M、stream_window_size=256K。
看来是没有修改成功。

@zyearn
Copy link
Member

zyearn commented Jul 16, 2019

我本地试了下,是可以正确设置的,把你起client程序的命令发一下吧

@supernovaer
Copy link

client端进程启动流程大概是这样的:
brpc::Server server; server.AddService(xxx); brpc::ServerOptions options; options.h2_settings.stream_window_size=brpc::policy::FLAGS_h2_client_stream_window_size; options.h2_settings.connection_window_size=brpc::policy::FLAGS_h2_client_connection_window_size; (这两个size的赋值是新加的,好像没什么用)server.Start(ip:port, &options);

client请求grpc的流程大概是这样的:
分别申请brpc::Controller cntl、Channel对象 channel,brpc::ChannelOptions options; options.protocol="h2:grpc"; options.connection_type="single";channel.Init(grpc_ip:port, &options);然后定义grpc服务proto service对应的xxx_Stub stub(&channel); stub.service_name(&cntl, &req, &rsp, NULL);

@supernovaer
Copy link

gdb断点看了下,进到H2StreamContext::ConsumeWindowSize这个函数的时候,第一个判断条件就没通过,_remote_window_left已经小于size了。
image
实际各个变量的情况如下图:
image
与H2StreamContext关联的H2Context对象_conn_ctx的_unack_local_settings里面初始化几个的window_size变量,值都是对的,是我通过h2_client_stream_window_size、h2_client_connection_window_size这两个gflag传进去的值,均>10M。
但是_remote_window_left的值却很小,百K级别,并且在第一次请求的时候,_remote_window_left就这么小了,导致一直报 ”remote_window_left is not enough“的错。

另外,每次请求虽然失败了,但最后还是触发了H2Context::OnSettings,从grpc server端ParseH2Settings(H2Settings* out, butil::IOBufBytesIterator& it, size_t n)这个函数也执行到了out->stream_window_size = value;这里,value也是M级别的,跟grpc server的配置是吻合的。
const int64_t window_diff =
static_cast<int64_t>(_remote_settings.stream_window_size)
- old_stream_window_size; window_diff的计算结果正常,也成功触发了AddWindowSize,但是下一次请求仍然是”remote_window_left is not enough“

@zyearn
Copy link
Member

zyearn commented Jul 16, 2019

FLAGS_h2_client_stream_window_size这个值是代表client的配置,一般是写在起程序的conf里的,不是赋给ServerOptions.h2_settings的,该值是server端的配置。一般用法是这样的,./client --h2_client_stream_window_size=xxx --h2_client_connection_window_size=yyy

@supernovaer
Copy link


赋值给ServerOptions.h2_settings是调试的时候加的,因为担心走到箭头这个分支里去了。
确实不需要加这段赋值的,最开始报window-size错的时候,也没加这段。
image

@supernovaer
Copy link

FLAGS_h2_client_stream_window_size这个值是代表client的配置,一般是写在起程序的conf里的,不是赋给ServerOptions.h2_settings的,该值是server端的配置。一般用法是这样的,./client --h2_client_stream_window_size=xxx --h2_client_connection_window_size=yyy

进程启动方式应该没问题,其他配置都正常读到的。
我是用配置文件解析gflags,启动命令:./bin -flagfile=flag.conf
代码里
int main(int argc, char* argv[]) {
// Parse gflags.
google::ParseCommandLineFlags(&argc, &argv, true);
brpc::Server server;
...
配置文件conf.flag里
--h2_client_connection_window_size=10485760
--h2_client_stream_window_size=10485760

@zyearn
Copy link
Member

zyearn commented Jul 17, 2019

@kaydxh @supernovaer 关于要先发一个小包来解决setting交换的问题,spec对这个问题有相关说明,当client还未收到server setting时,可以先发数据,如下图
image
这种情况下,直接跳过流控就好,会在近期修复这个问题。

@supernovaer
Copy link

把H2Settings构造函数connect_window_size的默认值加大,临时解决这个问题了。
期待官方的修复~

@supernovaer
Copy link

之前调试没弄明白的几个问题,现在大概也明白了:

  1. h2_client_connection_window_size、h2_client_stream_window_size这俩配置参数,只赋值给了_unack_local_settings,没有影响到remote_settings,所以设这两个gflag参数是没有用的
  2. req帧虽然因为windows_size的问题,没有发出去,但是setting帧还是发出去了
  3. 虽然setting帧顺利收发了,也解析出了grpc server的设置(已经调大过了),但是因为请求失败,连接断开了,Connection reset by peer;_remote_window_left应该是与socket绑定的,连接断开,下次发请求又建了新的socket,然后又用了H2Settings构造函数的默认size
    image

@supernovaer
Copy link

把H2Settings构造函数connect_window_size的默认值加大,临时解决这个问题了。
期待官方的修复~

说错了,是把 stream_window_size的默认值加大

@zyearn
Copy link
Member

zyearn commented Jul 18, 2019

h2_client_connection_window_size/h2_client_stream_window_size是有用的,这是client的参数,你赋给了serveroptions是没用的,这是让client的配置错误地在server上应用,这是client告诉server,自己这里有多大的windowsize,你需要在client的程序启动时设置

@supernovaer
Copy link

h2_client_connection_window_size/h2_client_stream_window_size是有用的,这是client的参数,你赋给了serveroptions是没用的,这是让client的配置错误地在server上应用,这是client告诉server,自己这里有多大的windowsize,你需要在client的程序启动时设置

  1. 赋给了serveroptions是没用的,这一点明白了,前面给它赋值是调试的时候胡乱加的,当时没想太明白
    2.直接设置这两个参数h2_client_connection_window_size/h2_client_stream_window_size也没有用,已经在client启动的时候设置了
  2. 设这两个参数也没用的原因,是因为AppendAndDestroySelf函数sctx->Init(ctx, id);这里,
    image
    image
    stream_window_size的初始值是写死的,跟上面的两个gflag变量并没有关系
  3. 这里是不是可以给H2Settings.stream_window_size新增一个初始值gflag变量?这里其实是server_remote_setting,含义上跟h2_client_xxx_window_size也不太一样

@supernovaer
Copy link

修改H2Settings构造函数stream_window_size的初始化值,这是我本地临时的解决方案。
非常期待官方的修复~
是不是可以在建立连接的时候,先发settings帧,获取server端的配置,再发请求?
或者在因为本地判定remote_window_size不够这个原因导致请求失败时,不要关闭socket,(因为其实setting帧已经发出去了,也已经收到server回包成功解析到server配置了),或者把当前socket已成功解析到的remote_settings缓存一下,作为后面新建socket的初始值(而不是用写死或gflag配置的默认值)?这样至少保证下一个请求是正常的。
小小的建议,没有经过成熟的思考

@zyearn
Copy link
Member

zyearn commented Jul 18, 2019

2. 设这两个参数也没用的原因,是因为AppendAndDestroySelf函数sctx->Init(ctx, id);这里,

在H2Context构造的时候会把默认值用FLAGS_h2_client_stream_window_size以及FLAGS_h2_client_max_frame_size替换,所以还是用的这两个参数

是不是可以在建立连接的时候,先发settings帧,获取server端的配置,再发请求?

sepc说可以先发请求以减少延迟

或者在因为本地判定remote_window_size不够这个原因导致请求失败时,不要关闭socket,(因为其实setting帧已经发出去了,也已经收到server回包成功解析到server配置了),或者把当前socket已成功解析到的remote_settings缓存一下,作为后面新建socket的初始值(而不是用写死或gflag配置的默认值)?这样至少保证下一个请求是正常的。

现在当某一个包比server remote window还大时,目前的做法是直接失败了。可以改成先发一个包,等对方的window_update发过来以后,再继续发下一个包,这个目前是个TODO

@zyearn
Copy link
Member

zyearn commented Jul 22, 2019

@kaydxh @supernovaer 关于要先发一个小包来解决setting交换的问题,spec对这个问题有相关说明,当client还未收到server setting时,可以先发数据,如下图
image
这种情况下,直接跳过流控就好,会在近期修复这个问题。

Fixed in #849

@zyearn zyearn closed this as completed Jul 22, 2019
@supernovaer
Copy link

Fixed in #849

Got it!
之前自己简单修了一版,H2Settings构造函数里_remote_settings.stream_window_size的初始值改成可配的,客户端启动的时候配大一点就可以了。
image

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