go實現(xiàn)thrift的網(wǎng)絡傳輸性能及需要注意問題示例解析
thrift簡介
thrift應該是目前支持編程語言種類最多的跨語言 rpc服務框架, http://thrift.apache.org/
thrift實現(xiàn)了完整的網(wǎng)絡服務,所以一般使用thrift時,會使用到thrift的服務框架。當然,也可以用自己已經(jīng)實現(xiàn)的網(wǎng)絡服務,用io流對接thrift接口的輸入輸出流實現(xiàn)thrift的接入。
無論是用thrift的網(wǎng)絡實現(xiàn),還是自己實現(xiàn)的網(wǎng)絡服務,只要對接thrift,在調用thrift接口實現(xiàn)rpc時,都是走thrift的網(wǎng)絡傳輸方式。
thrift的網(wǎng)絡傳輸實現(xiàn)方式 不適合也不支持壓力較大的網(wǎng)絡傳輸需求。實際上,調用一次thrift接口,并不是只調一次網(wǎng)絡io寫數(shù)據(jù),而是拆分為多次寫數(shù)據(jù)傳送。
調用一個thrift 的接口發(fā)送數(shù)據(jù)時,thrift會將這個操作拆分為幾個操作:
調用thrift的方法時:thrift會找到這個方法所在的對象,調用write方法,
在write方法在,分別對各個參數(shù),依次調用 writeFieldBegin,writeXXX(具體參數(shù)類型) ,WriteFieldStop 等函數(shù)
每次調用也同時調用網(wǎng)絡io寫相應數(shù)據(jù).
以目前最新的thrift-0.18.1實現(xiàn)為例
go的實現(xiàn)
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 } //第一個參數(shù) 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 } //第二個參數(shù),操作類似第一個參數(shù) 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->
- writeField1->WriteFieldBegin-> WriteByte-> io
- -> Write16 -> io
- ->WriteBinary-> Write32 -> io
- ->Write -> io
- writeField2->WriteFieldBegin-> WriteByte -> io
- ->Write16 -> io
- ->Write64-> Write -> io
- WriteFieldStop ->io
可以看到,一次簡單的方法調用,如果方法中有兩個參數(shù), 則至少有8次io流寫數(shù)據(jù)調用。
如果參數(shù)多時,或是參數(shù)中一個結構體的變量多時,則會有更多的io流寫數(shù)據(jù)調用。
在海量的網(wǎng)絡傳輸中,這樣的傳輸方式,網(wǎng)絡io流寫數(shù)據(jù)調用成倍增加,海量網(wǎng)絡io數(shù)據(jù)寫入導致性能急劇下降。
thrift設計的傳輸層提供了zlib協(xié)議壓縮,在zlib壓縮發(fā)送的情況下,將數(shù)據(jù)進行了整體壓縮收發(fā),zlib分為2次發(fā)送后,接收端再解壓;
以go為例子:
可以在 compress/flate 看到zlib的寫數(shù)據(jù)最終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數(shù)據(jù)寫入
所以,在海量調用thrift方法的情況下,zlib模式的性能要遠超非zlib的情況。但是zlib壓縮會比較消耗內存,大量使用時可能導致頻繁gc,也可能導致性能下降。當然,即使如此,大部分情況下zlib傳輸依然比非zlib傳輸?shù)男阅芤迷S多。
其他語言的實現(xiàn)
比如 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(); }
傳輸方式都是相似的
實現(xiàn)方式各個語言都相似,當然,數(shù)據(jù)寫入順序肯定是一樣的。
以上就是go實現(xiàn)thrift的網(wǎng)絡及傳輸性能需要注意問題示例解析的詳細內容,更多關于go thrift的網(wǎng)絡傳輸?shù)馁Y料請關注腳本之家其它相關文章!
相關文章
Golang?errgroup?設計及實現(xiàn)原理解析
這篇文章主要為大家介紹了Golang?errgroup?設計及實現(xiàn)原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08