go通过thrift连接hbase_关于thrift协议改进畅想

Thrift简介

thrift协议你主要提供3种编解码格式协议,Binary、Compact、SimpleJson(还有debug、virtual);其中最常用的是Binary协议,其传输层一般以BufferedTransport、BufferedTransport居多;这里讨论主要以Binary协议为基础。本身来说,Binary协议不做任何压缩,只是为了支持跨系统网络传输(当然也可以做本地编解码工具)。

protobuf、flat对比

Compact协议对标protobuf编码,主要是利用varint压缩;Binary对标的是flat编码

和protobuf对比,他们thrift区别在于:

  • protobuf、flat协议编解码方式只有一种,而thrift有3种
  • protobuf以小端序传输,thrift以大端序传输,它们都只是跨系统网络传输;flat仅适用于机型系统(大小端相同)
  • protobuf支持反射、thrift不支持
  • protobuf3.0后支持更丰富的类型比如time、date、duration,thrift不支持

Binary编码介绍

Binary整体编码形式如下:

简单类型(byte、int8、int16、int32、int64、float64): 数据类型(1字节)+ 数据

字符串(string):数据类型(1字节)+ 数据长度(4字节)+数据

数组([]byte、[]int32、[]int64等):数据类型(1字节)+数组长度(4字节)+ 连续数据...

数组结构([]struct):数据类型(1字节)+数组长度(4字节)+ 连续(struct数据)...

字符串数组([]string):数据类型(1字节)+数组长度(4字节)+连续 (字符串长度+数据)...

map:数据类型(1字节)+key类型+value类型+map长度(4字节)+连续(key+value)...

对于非字符串、[]byte类型的超过2个连续字节的编码(包含长度),都是大端序

奇思妙想

如上面所说,Binary编码不做任何数据压缩,且是大端编码,这种方式是否足够好呢?对于编解码工具来说,除了能做跨网络传输编解码协议,好像看不到任何其他收益;

想想的确是这样

  • 相比protobuf,不能做压缩
  • 相比flat,性能上有损

既然,thrift的compact本身是对标protobuf,binary是对标flat,那我们可以可以把Binary编解码性能提升到等同flat呢???

答案是可以!!!

但是有一个问题待解决,flat是无法跨系统网络传输的,因为编码本身是依赖机器自身大小端。可以绕过去吗?

可以假如我们在编码的时候,把机器大小端信息带上去不就可以了!!!标识放到哪?放到Version头部即可!

具体实现

在编码的时候,新定义一种ProtocolID类型,叫ProtocolIDFlatLtlEnd(Flate小端序编码),因为ProtocolIDBinary本身是大端序(可以看做是flat变种),需要再新增ProtocolIDFlatBigEnd。

以go为例

const (

ProtocolIDBinary ProtocolID = 0

ProtocolIDJSON ProtocolID = 1

ProtocolIDCompact ProtocolID = 2

ProtocolIDDebug ProtocolID = 3

ProtocolIDVirtual ProtocolID = 4

ProtocolIDSimpleJSON ProtocolID = 5

ProtocolIDFlatLtlEnd ProtocolID = 6

)

Binary统一flat编码

  • 编码:
    • 先判断本机大小端:如果是大端,则头部写ProtocolID= ProtocolIDBinary,否则头部写ProtocolID= ProtocolIDFlatLtlEnd
    • 把数据字段拷贝到内存即可,无需先做大端转换,再拷贝;特别的,对于数组类型[]int16,[]int32,[]int64,直接大段数据拷贝,无需遍历做端转换后一个个拷贝
  • 解码:
    • 获取协议头部ProtocolID,如果ProtocolID=ProtocolIDBinary且本机是大端;或者如果ProtocolID= ProtocolIDFlatLtlEnd且本机是小端,直接从内存copy数据到字段即可,无需做大端转换;特别的,对于数组类型[]int16,[]int32,[]int64,直接大段数据拷贝到数组,无需多次分段大端转换再赋值

如何判断大小端,参考如下代码:

var isLittleEndian bool = true

func init() {

a := uint16(0x1234)

ptr := (*byte)(unsafe.Pointer(&a))

if *ptr == 0x12 {

isLittleEndian = false

}

}

兼容性

Binary是大端序,即ProtocolID= ProtocolIDBinary;为了保持兼容,必须设置默认ProtocolID= ProtocolIDBinary,并提供一个接口是否开启支持FlatLtlEnd。

编码过程变成如下:

  • 如果本机是大端,直接flat编码,并且设置ProtocolID= ProtocolIDBinary;
  • 如果本机是小端,并且用户设置了开启支持FlatLtlEnd,则直接flat编码,设置ProtocolID= ProtocolIDFlatLtlEnd
  • 如果本机是小端,并且用户未设置开启支持FlatLtlEnd,仍然按照大端编码;和现有编解码保持一致

如果要开启FlatLtlEnd,则下游服务方必须开启支持FlatLtlEnd解码,这个直接通过header_protocol协议探测即可--服务端在业务数据header部加一个是否支持FlatLtlEnd即可。

那么上面的编码过程进一步简化成自动化探测协议,达到完美兼容现有thrift协议,整个过程如下图所示:

这里4种情况:

  • client升级支持FlatLtlEnd,server没升级
  • client升级支持FlatLtlEnd,server升级支持FlatLtlEnd
  • client没升级,server没升级
  • client没升级,server升级支持FlatLtlEnd

client升级支持FlatLtlEnd,server升级支持FlatLtlEnd

bed2ed8935d693b54f2c4e54d52d16ff.png

client支持FlatLtlEnd、server不支持

41a7f568c383ebca78a99d8ad4b9bfbf.png

如果server某次升级支持FlatLtlEnd,那么场景回归到第一种情形

client不支持、server支持FlatLtlEnd

2bf6c32c6f8bada34f808d5016627450.png

如果client某次升级支持FlatLtlEnd,那么场景回归到第一种情形

高性能

一般的,thrift rpc大部分场景是内部局域网通信,机器大小端一致,那么thrift编码完全退化成flat编码;会有以下性能提升

  • 省去大端编码开销,直接拷贝
  • 对于数组类型[]uint8,[]uint16,[]uint32,[]uint64,[]float32,[]float64,直接按照内存拷贝,无需遍历先大端后copy;且能充分利用avx、avx2、avx512等特殊指令,能够享受并行处理带来的性能提升(提升20倍+);

初步估算:改进后的编解码性能提升,在2~20+倍

性能测试

测试代码代码如下(简要模拟thrift编解码、优化后的flat编解码)

buptbill220/gooptlib​github.com
4ff083b8681a744ac132ec6586d22815.png

测试结论如下

  • 优化后的thrift(Binary)编解码效率明显高于原生的编解码,平均在100%+(考虑到原生的bufio效率低,实际高300%以上,参考
buptbill220/thrift_opt​github.com
4ff083b8681a744ac132ec6586d22815.png

  • 特别对于数组较多的场景,优化后的thrift编解码,性能提升2000%;对于map多的可以改成平行数组优化

测试结果如下


# 模拟不同测试用例下,观测新thrift编码性能提升数据;

观察BenchmarkThrift(最原始),BenchmarkThriftNewOpt(最终优化)的数据对比

数据结构

type Data struct {

A int32

B int64

C []int64

D map[int]string

E []string

F []float64

}

case 1:所有数据类型比较平均,且数据量小;提升50%

===========================================================================

var data = &Data{

A: 123,

B: 123456789,

C: make([]int64, 5),

D: map[int]string{

12: "23424",

23: "34324",

44: "xxsdsfsfd",

64: "2sxdrwr",

},

E: []string{"2334", "23234234"},

F: []float64{1.0,23,23},

}

BenchmarkThrift-4 6316356 217 ns/op

BenchmarkThriftNew-4 6065745 213 ns/op

BenchmarkThriftOpt-4 7148114 229 ns/op

BenchmarkThriftNewOpt-4 8790902 144 ns/op

===========================================================================

case 2:所有数据类型比较平均,且数据量小;提升100%

===========================================================================

var data = &Data{

A: 123,

B: 123456789,

C: make([]int64, 20),

D: map[int]string{

12: "23424",

23: "34324",

344: "xcxfsf",

545: "xcfsfsdffd",

43: "2342344",

9: "jhdkajhf",

87: "sdfsf",

},

E: []string{"2334", "23234234", "sdfsdf", "sdfsfsf", "sdfsfsfsff"},

F: []float64{1.0,23,23, 3242, 34, 345345, 345, 435, 243},

}

BenchmarkThrift-4 2750547 505 ns/op

BenchmarkThriftNew-4 3740912 418 ns/op

BenchmarkThriftOpt-4 4432053 289 ns/op

BenchmarkThriftNewOpt-4 4456192 252 ns/op

===========================================================================

case 3:所有数据类型比较平均,且数据量中(数组偏多);提升1719%

===========================================================================

var data = &Data{

A: 123,

B: 123456789,

C: make([]int64, 1024),

D: map[int]string{

12: "23424",

23: "34324",

344: "xcxfsf",

545: "xcfsfsdffd",

43: "2342344",

9: "jhdkajhf",

87: "sdfsf",

},

E: []string{"2334", "23234234", "sdfsdf", "sdfsfsf", "sdfsfsfsff"},

F: []float64{1.0,23,23, 3242, 34, 345345, 345, 435, 243, },

}

BenchmarkThrift-4 195631 5930 ns/op

BenchmarkThriftNew-4 3081625 391 ns/op

BenchmarkThriftOpt-4 402711 2845 ns/op

BenchmarkThriftNewOpt-4 3696662 326 ns/op

===========================================================================

case 4:所有数据类型比较平均,且数据量多(数组偏多,考虑内存扩容);提升2192%

===========================================================================

var data = &Data{

A: 123,

B: 123456789,

C: make([]int64, 10240),

D: map[int]string{

12: "23424",

23: "34324",

344: "xcxfsf",

545: "xcfsfsdffd",

43: "2342344",

9: "jhdkajhf",

87: "sdfsf",

},

E: []string{"2334", "23234234", "sdfsdf", "sdfsfsf", "sdfsfsfsff"},

F: []float64{1.0,23,23, 3242, 34, 345345, 345, 435, 243, },

}

BenchmarkThrift-4 26301 49450 ns/op

BenchmarkThriftNew-4 533421 2164 ns/op

BenchmarkThriftOpt-4 44829 28206 ns/op

BenchmarkThriftNewOpt-4 477034 2157 ns/op

===========================================================================

case 5:所有数据类型比较平均,且数据量多(数组偏多,不考虑内存扩容);提升1867%(猜测cache miss影响)

===========================================================================

var data = &Data{

A: 123,

B: 123456789,

C: make([]int64, 10240),

D: map[int]string{

12: "23424",

23: "34324",

344: "xcxfsf",

545: "xcfsfsdffd",

43: "2342344",

9: "jhdkajhf",

87: "sdfsf",

},

E: []string{"2334", "23234234", "sdfsdf", "sdfsfsf", "sdfsfsfsff"},

F: []float64{1.0,23,23, 3242, 34, 345345, 345, 435, 243, },

}

BenchmarkThrift-4 21034 47975 ns/op

BenchmarkThriftNew-4 439399 2506 ns/op

BenchmarkThriftOpt-4 39430 27047 ns/op

BenchmarkThriftNewOpt-4 535525 ns/op

===========================================================================

case 6: 所有数据类型比较平均,且数据量多(非数组偏多,不考虑内存扩容);提升30%(map遍历性能比较差,指令cache miss,数据cache miss)

===========================================================================

var data = &Data{

A: 123,

B: 123456789,

C: make([]int64, 10240),

D: map[int]string{

12: "23424",

23: "34324",

344: "xcxfsf",

545: "xcfsfsdffd",

43: "2342344",

9: "jhdkajhf",

87: "sdfsf",

},

E: []string{"2334", "23234234", "sdfsdf", "sdfsfsf", "sdfsfsfsff"},

F: []float64{1.0,23,23, 3242, 34, 345345, 345, 435, 243, },

}

BenchmarkThrift-4 45409 27783 ns/op

BenchmarkThriftNew-4 40582 26488 ns/op

BenchmarkThriftOpt-4 52592 21835 ns/op

BenchmarkThriftNewOpt-4 52173 21219 ns/op

===========================================================================

要在Python中使用Thrift连接HBase,您需要按照以下步骤进行设置: 1. 安装所需的依赖项: 您需要安装`thrift`和`happybase`这两个Python库。可以使用以下命令进行安装: ```bash pip install thrift happybase ``` 2. 生成HBaseThrift代码: 使用Thrift工具生成HBaseThrift代码。您可以使用以下命令: ```bash thrift -r --gen py hbase.thrift ``` 这将生成Python的Thrift代码文件。 3. 创建HBase连接: 在Python脚本中,您需要首先创建一个HBase连接。示例代码如下: ```python import happybase connection = happybase.Connection(host='localhost', port=9090) ``` 4. 执行HBase操作: 在创建了HBase连接之后,您可以使用`connection`对象执行各种HBase操作,例如创建表、插入数据、获取数据等。以下是一些示例代码: - 创建表: ```python connection.create_table( 'mytable', { 'cf': dict(max_versions=10), } ) ``` - 插入数据: ```python table = connection.table('mytable') table.put( b'row1', { b'cf:col1': b'value1', b'cf:col2': b'value2', } ) ``` - 获取数据: ```python table = connection.table('mytable') row = table.row(b'row1') print(row) ``` - 删除数据: ```python table = connection.table('mytable') table.delete(b'row1') ``` 这只是一些示例代码,您可以根据需要使用其他HappyBase方法来执行更多操作。 5. 关闭连接: 当您完成HBase操作后,记得关闭连接以释放资源: ```python connection.close() ``` 请注意,为了成功执行这些操作,您需要确保HBase正在运行并且在指定的主机和端口上进行监听。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值