HTTP/2介绍
下面关于HTTP/2的理论参考摘抄于《HTTP/2 协议抓包实战》
HTTP/1.1的问题
-
没有充分利用TCP连接资源
1.1 TCP协议是一个全双工协议,但在HTTP/1.1为一问一答的半双工形式
(1)HTTP/1.1使用TCP协议中的Keep Alive属性复用现有TCP连接(一问一答)
(2)HTTP/1.0每次请求都断开TCP连接1.2 如果页面需要多个资源,而浏览器限制同域名下的请求并发度,所以通过多线程提高并发不可取
-
头部与公参数据所占用的巨大比例
2.1 HTTP/1.1 的无状态特性带来的巨大 HTTP 头部与公参,这些数据很可能在用户的使用过程中是不会发生变化的
HTTP/2怎样解决问题
- 访问该网站:http2.akamai.com/demo ,对 HTTP/2 相较于 HTTP/1.1 速度的提升有一个直观的感受
1.1 在 HTTP/1.1 请求过程中,浏览器共使用 6 个并发 TCP 链接请求图片数据(符合上文中的提到 Chrome 并发度为 6 的限制)
1.2 HTTP/2 中因为使用了多路复用,仅在一个 TCP 连接上传输数据,但其传输速度仍然有明显的提高
- 头部压缩(HPACK算法:有状态 -> 问题2)
2.1 在客户端和服务器端存储Header的Key-Value数据,下次使用该数据只需要传索引号
2.2 对整数和字符串采用哈夫曼编码(或ASCII 编码)来进一步压缩 - HTTP/2采用二进制的消息格式
3.1 用HEADERS帧存放头数据、DATA帧存放请求正文数据
3.2 每个流在HTTP/2连接有其内唯一的一个ID标志(多路复用 -->问题1)
(1)一个流为一个二进制的双向传输通道
(2)同一个消息的所有往返帧都处于唯一的流中,在里面传输一系列有先后顺序的数据帧
Dubbo如何实现的
- 《dubbo3.x消费端源码浅析以及Triple协议的实现》中有介绍dubbo Triple协议的实现的地方,下面我们就来了解具体如何实现的
- 我们来看一下TripleHttp2Protocol是如何配置pipeline的?
2.1 通过Http2FrameCodec将HTTP/2的frames解码为Http2Frame对象,并进行pipeline传播
2.2 TripleServerConnectionHandler只对Ping帧和GoAway帧处理,其他帧都直接进行pipeline传播
2.3 Http2MultiplexHandler是一个混合处理器,将多个handler添加到pipeline中
- Http2MultiplexHandler中重点关注TripleHttp2FrameServerHandler、GrpcDataDecoder、TripleServerInboundHandler
3.1 TripleHttp2FrameServerHandler处理Headers帧和Data帧处理
3.2 GrpcDataDecoder:如果有压缩标志,则进行相应的解压缩
3.3 TripleServerInboundHandler:将数据放入serverStream
TripleHttp2FrameServerHandler
- 处理Headers帧:TripleHttp2FrameServerHandler#onHeadersRead
1.1 为invoker的url创建一个UnaryServerStream
1.2 将UnaryServerStream绑定channel的tri_server_stream属性中(方便后面的handler获取)final AbstractServerStream stream = AbstractServerStream.newServerStream(invoker.getUrl(), isUnary); stream.service(providerModel.getServiceModel()) .invoker(invoker) .methodName(methodName) .setDeCompressor(deCompressor) .subscribe(transportObserver); channel.attr(TripleConstant.SERVER_STREAM_KEY).set(stream);
- 处理Data帧:TripleHttp2FrameServerHandler#onDataRead
2.1 将msg传递给pipeline中的其他handler
2.2 所有handler处理完后,调用serverStream的完成方法(详细见下面)public void onDataRead(ChannelHandlerContext ctx, Http2DataFrame msg) throws Exception { super.channelRead(ctx, msg.content()); if (msg.isEndStream()) { final AbstractServerStream serverStream = ctx.channel().attr(TripleConstant.SERVER_STREAM_KEY).get(); if (serverStream != null) { // 在TripleServerInboundHandler中会调用serverStream.inboundTransportObserver().onData(data,false)将数据放入serverStream中 serverStream.inboundTransportObserver().onComplete(); } } }
UnaryServerStream的onComplete方法
- 将调用任务丢给SerializingExecutor线程池执行
- 调用方法在UnaryServerStream$UnaryServerTransportObserver#invoke
public void invoke() { //使用头元数据构建RpcInvocation并执行headerFilter RpcInvocation invocation = buildUnaryInvocation(getHeaders(), getData()); // 对data数据进行序列化 ->unpack方法最终会调用SingleProtobufUtils#deserialize进行反序列化 inv.setArguments(new Object[]{unpack(data, getMethodDescriptor().getParameterClasses()[0])}); // FilterChainBuilder$CallbackRegistrationInvoker#invoke 使用过滤器链直到调用业务服务方法 final Result result = getInvoker().invoke(invocation); }
束语
- 本篇文章通过HTTP1的问题来介绍HTTP2是如何解决的,然后看看Dubbo是如何实现HTTP2服务的
- 写完本篇博客后,虽然对HTTP2有了进一步的认识,但心里又有其他疑惑(UnaryServerStream和HTTP2的虚拟流是同一个东西吗?关于头部压缩的具体实现没看到)。有序篇幅原因,留到下一篇文章继续分析吧