关于高性能:thrift的网络传输性能和需要注意的问题

6次阅读

共计 4210 个字符,预计需要花费 11 分钟才能阅读完成。

起源:thrift 的网络传输性能和须要留神的问题
thrift 应该是目前反对编程语言品种最多的跨语言 rpc 服务框架,http://thrift.apache.org/

thrift 实现了残缺的网络服务,所以个别应用 thrift 时,会应用到 thrift 的服务框架。当然,也能够用本人曾经实现的网络服务,用 io 流对接 thrift 接口的输入输出流实现 thrift 的接入。

无论是用 thrift 的网络实现,还是本人实现的网络服务,只有对接 thrift,在调用 thrift 接口实现 rpc 时,都是走 thrift 的网络传输方式。

thrift 的网络传输实现形式 不适宜也不反对压力较大的网络传输需要。实际上,调用一次 thrift 接口,并不是只调一次网络 io 写数据,而是拆分为屡次写数据传送。

调用一个 thrift 的接口发送数据时,thrift 会将这个操作拆分为几个操作:

调用 thrift 的办法时:thrift 会找到这个办法所在的对象,调用 write 办法,

在 write 办法在,别离对各个参数,顺次调用 writeFieldBeginwriteXXX(具体参数类型),WriteFieldStop 等函数
每次调用也同时调用网络 io 写相应数据.

以目前最新的 thrift-0.18.1 实现为例

比方 go 的实现:

func (p *ItnetPonMergeArgs) Write(ctx context.Context, oprot thrift.TProtocol) error {if err := oprot.WriteStructBegin(ctx, "PonMerge_args"); err != nil {return thrift.PrependError(fmt.Sprintf("%T write struct begin error:", p), err) }
  if p != nil {if err := p.writeField1(ctx, oprot); err != nil {return err}
    if err := p.writeField2(ctx, oprot); err != nil {return err}
  }
  if err := oprot.WriteFieldStop(ctx); err != nil {return thrift.PrependError("write field stop error:", err) }
  if err := oprot.WriteStructEnd(ctx); err != nil {return thrift.PrependError("write struct stop error:", err) }
  return nil
}
// 第一个参数
func (p *ItnetPonMergeArgs) writeField1(ctx context.Context, oprot thrift.TProtocol) (err error) {if err := oprot.WriteFieldBegin(ctx, "pblist", thrift.LIST, 1); err != nil { //WriteFieldBegin
    return thrift.PrependError(fmt.Sprintf("%T write field begin error 1:pblist:", p), err) }
  if err := oprot.WriteListBegin(ctx, thrift.STRUCT, len(p.Pblist)); err != nil {return thrift.PrependError("error writing list begin:", err)
  }
  for _, v := range p.Pblist {if err := v.Write(ctx, oprot); err != nil {return thrift.PrependError(fmt.Sprintf("%T error writing struct:", v), err)
    }
  }
  if err := oprot.WriteListEnd(ctx); err != nil {return thrift.PrependError("error writing list end:", err)
  }
  if err := oprot.WriteFieldEnd(ctx); err != nil {return thrift.PrependError(fmt.Sprintf("%T write field end error 1:pblist:", p), err) }
  return err
}
// 第二个参数,操作相似第一个参数
func (p *ItnetPonMergeArgs) writeField2(ctx context.Context, oprot thrift.TProtocol) (err error) {if err := oprot.WriteFieldBegin(ctx, "id", thrift.I64, 2); err != nil {return thrift.PrependError(fmt.Sprintf("%T write field begin error 2:id:", p), err) }
  if err := oprot.WriteI64(ctx, int64(p.ID)); err != nil {return thrift.PrependError(fmt.Sprintf("%T.id (2) field write error:", p), err) }
  if err := oprot.WriteFieldEnd(ctx); err != nil {return thrift.PrependError(fmt.Sprintf("%T write field end error 2:id:", p), err) }
  return err
}

调用 Pon(ItnetPonMergeArgs)办法的 thrift 传输程序是:

Write->

  1. writeField1->WriteFieldBegin-> WriteByte-> io
  2. -> Write16 -> io
  3. ->WriteBinary-> Write32 -> io
  4. ->Write -> io
  5. writeField2->WriteFieldBegin-> WriteByte -> io
  6. ->Write16 -> io
  7. ->Write64-> Write -> io
  8. WriteFieldStop ->io

能够看到,一次简略的办法调用,如果办法中有两个参数,则至多有 8 次 io 流写数据调用。

如果参数多时,或是参数中一个构造体的变量多时,则会有更多的 io 流写数据调用。

在海量的网络传输中,这样的传输方式,网络 io 流写数据调用成倍增加,海量网络 io 数据写入导致性能急剧下降。

thrift 设计的传输层提供了 zlib 协定压缩,在 zlib 压缩发送的状况下,将数据进行了整体压缩收发,zlib 分为 2 次发送后,接收端再解压;

以 go 为例子:

能够在 compress/flate 看到 zlib 的写数据最终 io 写入调用:

func (d *compressor) syncFlush() error {
    if d.err != nil {return d.err}
    d.sync = true
    d.step(d)
    if d.err == nil {d.w.writeStoredHeader(0, false)   // 第一次调用
        d.w.flush()                       // 第二次调用
        d.err = d.w.err
    }
    d.sync = false
    return d.err
}
// 两次 io 数据写入 

所以,在海量调用 thrift 办法的状况下,zlib 模式的性能要远超非 zlib 的状况。然而 zlib 压缩会比拟耗费内存,大量应用时可能导致频繁 gc,也可能导致性能降落。当然,即使如此,大部分状况下 zlib 传输仍然比非 zlib 传输的性能要好许多。

其余语言的实现:比方 java:

public void write(org.apache.thrift.protocol.TProtocol oprot, SelectByIdxLimit_args struct) throws org.apache.thrift.TException {struct.validate();
        oprot.writeStructBegin(STRUCT_DESC);
        if (struct.name != null) {oprot.writeFieldBegin(NAME_FIELD_DESC);  //io 调用
          oprot.writeString(struct.name);          //io 调用
          oprot.writeFieldEnd();}
        if (struct.column != null) {oprot.writeFieldBegin(COLUMN_FIELD_DESC);  //io 调用
          oprot.writeString(struct.column);           //io 调用
          oprot.writeFieldEnd();}
        if (struct.value != null) {oprot.writeFieldBegin(VALUE_FIELD_DESC);   //io 调用
          {oprot.writeListBegin(new org.apache.thrift.protocol.TList(org.apache.thrift.protocol.TType.STRING, struct.value.size()));
            for (java.nio.ByteBuffer _iter49 : struct.value)
            {oprot.writeBinary(_iter49);          //io 调用
            }
            oprot.writeListEnd();}
          oprot.writeFieldEnd();}
        oprot.writeFieldBegin(START_ID_FIELD_DESC); //io 调用
        oprot.writeI64(struct.startId);             //io 调用       
        oprot.writeFieldEnd();
        oprot.writeFieldBegin(LIMIT_FIELD_DESC);    //io 调用
        oprot.writeI64(struct.limit);                //io 调用
        oprot.writeFieldEnd();
        oprot.writeFieldStop();                    //io 调用
        oprot.writeStructEnd();}

传输方式都是类似的
实现形式各个语言都类似,当然,数据写入程序必定是一样的。


有任何问题或倡议请 Email:donnie4w@gmail.com 或 http://tlnet.top/contact 发信给我,谢谢!

正文完
 0