Kafka消息格式中的变长字段(Varints)
歡迎支持筆者新作:《深入理解Kafka:核心設計與實踐原理》和《RabbitMQ實戰(zhàn)指南》,同時歡迎關注筆者的微信公眾號:朱小廝的博客。
歡迎跳轉到本文的原文鏈接:https://honeypps.com/mq/varints-of-kafka-log-format/
kafka從0.11.0版本開始所使用的消息格式版本為v2,這個版本的消息相比于v0和v1的版本而言改動很大,同時還參考了Protocol Buffer而引入了變長整型(Varints)和ZigZag編碼。為了更加形象的說明問題,首先我們來了解一下變長整型。
Varints是使用一個或多個字節(jié)來序列化整數(shù)的一種方法。數(shù)值越小,其所占用的字節(jié)數(shù)就越少。Varints中每個字節(jié)都有一個位于最高位的msb位(most significant bit),除了最后一個字節(jié)外其余msb位都設置為1,最后一個字節(jié)的msb位設置為0。這個msb位表示其后的字節(jié)是否和當前字節(jié)一起來表示同一個整數(shù)。除msb位之外,剩余的7位用于存儲數(shù)據(jù)本身,這種表示類型又稱之為Base 128。通常而言,一個字節(jié)8位可以表示256個值,所以稱之為Base 256,而這里只能用7位表示,2的7次方即為128。Varints中采用小端字節(jié)序,即最小的字節(jié)放在最前面。
舉個例子,比如數(shù)字1,它只占一個字節(jié),所以msb位為0:
0000 0001
再舉一個復雜點的例子,如數(shù)字300:
1010 1100 0000 0010
300的二進制表示原本為:0000 0001 0010 1100 = 256+32+8+4=300,那么為什么300的變長表示為上面的這種形式?
首先我們先去掉每個字節(jié)的msb位,表示如下:
1010 1100 0000 0010
-> 010 1100 000 0010
如前所述,varints是小端字節(jié)序的布局方式,所以這里兩個字節(jié)的位置需要翻轉一下:
010 1100 000 0010
-> 000 0010 010 1100 (翻轉)
-> 000 0010 ++ 010 1100
-> 0000 0001 0010 1100 = 256+32+8+4=300
Varints可以用來表示int32、int64、uint32、uint64、sint32、sint64、bool、enum等類型。在實際使用過程中,如果當前字段可以表示為負數(shù),那么對于int32/int64和sint32/sint64而言,它們在進行編碼時存在著較大的區(qū)別。比如你使用int64表示一個負數(shù),那么哪怕是-1,其編碼后的長度始終為10個字節(jié)(可以通過下面的代碼來測試長度),就如同對待一個很大的無符號長整型一樣。為了使得編碼更加的高效,Varints使用了ZigZag的編碼方式。
public int sizeOfLong(int v) {int bytes = 1;while ((v & 0xffffffffffffff80L) != 0L) {bytes += 1;v >>>= 7;}return bytes; }ZigZag編碼以一種鋸齒形(zig-zags)的方式來回穿梭于正負整數(shù)之間,以使得帶符號整數(shù)映射為無符號整數(shù),這樣可以使得絕對值較小的負數(shù)仍然享有較小的Varints編碼值,比如-1編碼為1,1編碼為2,-2編碼為3,參考下表:
| 0 | 0 |
| -1 | 1 |
| 1 | 2 |
| -2 | 3 |
| 2147483647 | 4294967294 |
| -2147483648 | 4294967295 |
對應的公式為:
(n << 1) ^ (n >> 31)
這是對于sint32而言的,sint64對應的公式為:
(n << 1) ^ (n >> 63)
以-1為例,其二進制表現(xiàn)形式為:1111 1111 1111 1111 1111 1111 1111 1111(補碼)。
(n << 1) = 1111 1111 1111 1111 1111 1111 1111 1110
(n >> 31) = 1111 1111 1111 1111 1111 1111 1111 1111
(n << 1) ^ (n >> 31) = 1
最終-1的Varints編碼為0000 0001,這樣原本用4字節(jié)表示的-1現(xiàn)在可以用1個字節(jié)來表示了。對于1而言就顯得非常簡單了,其二進制表現(xiàn)形式為:0000 0000 0000 0000 0000 0000 0000 0001。
(n << 1) = 0000 0000 0000 0000 0000 0000 0000 0010
(n >> 31) = 0000 0000 0000 0000 0000 0000 0000 0000
(n << 1) ^ (n >> 31) = 2
最終1的Varints編碼為0000 0010,也只占用一個字節(jié)。
前面說過Varints中的一個字節(jié)中只有7位是有效數(shù)值位,即只能表示128個數(shù)值,轉變成絕對值之后其實質上只能表示64個數(shù)值。比如對于消息體長度而言,其值肯定是個大于等于0的正整數(shù),那么一個字節(jié)長度的Varints最大只能表示63(從0開始計)。對于64而言,其二進制表示為:
0100 0000
經(jīng)過ZigZag處理后為:
1000 0000 ^ 0000 0000 = 1000 0000
每個字節(jié)的低7位是有效數(shù)值位,所以1000 0000進一步轉變?yōu)?#xff1a;
000 0001 000 0000
而Varints又是小端字節(jié)序,所以需要翻轉一下位置:
000 0000 000 0001
設置非最后一個字節(jié)的msb位為1,最后一個字節(jié)的msb位為0,最終有:
1000 0000 0000 0001
所以最終64表示為:1000 0000 0000 0001,而63卻表示為:0111 1110。
具體的編碼實現(xiàn)如下(針對int32類型):
public static void writeVarint(int value, ByteBuffer buffer) {int v = (value << 1) ^ (value >> 31);while ((v & 0xffffff80) != 0L) {byte b = (byte) ((v & 0x7f) | 0x80);buffer.put(b);v >>>= 7;}buffer.put((byte) v); }對應的解碼實現(xiàn)如下(針對int32類型):
public static int readVarint(ByteBuffer buffer) {int value = 0;int i = 0;int b;while (((b = buffer.get()) & 0x80) != 0) {value |= (b & 0x7f) << i;i += 7;if (i > 28)throw illegalVarintException(value);}value |= b << i;return (value >>> 1) ^ -(value & 1); }回顧一下kafka v0和v1版本的消息格式,如果消息本身沒有key,那么key length字段為-1,int類型的需要4個字節(jié)來保存,而如果采用Varints來編碼則只需要一個字節(jié)。根據(jù)Varints的規(guī)則可以推導出0-63之間的數(shù)字占1個字節(jié),64-8191之間的數(shù)字占2個字節(jié),8192-1048575之間的數(shù)字占3個字節(jié)。而kafka broker的配置message.max.bytes的默認大小為1000012(Varints編碼占3個字節(jié)),如果消息格式中與長度有關的字段采用Varints的編碼的話,絕大多數(shù)情況下都會節(jié)省空間,而v2版本的消息格式也正是這樣做的。
不過需要注意的是Varints并非一直會省空間,一個int32最長會占用5個字節(jié)(大于默認的4字節(jié)),一個int64最長會占用10字節(jié)(大于默認的8字節(jié))。下面代碼展示如何計算一個int32占用的字節(jié)個數(shù):
public static int sizeOfVarint(int value) {int v = (value << 1) ^ (value >> 31);int bytes = 1;while ((v & 0xffffff80) != 0L) {bytes += 1;v >>>= 7;}return bytes; }有關int32/int64的更多實現(xiàn)細節(jié)可以參考org.apache.kafka.common.utils.ByteUtils。
歡迎跳轉到本文的原文鏈接:https://honeypps.com/mq/varints-of-kafka-log-format/
歡迎支持筆者新作:《深入理解Kafka:核心設計與實踐原理》和《RabbitMQ實戰(zhàn)指南》,同時歡迎關注筆者的微信公眾號:朱小廝的博客。
總結
以上是生活随笔為你收集整理的Kafka消息格式中的变长字段(Varints)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Scala与Java集合互转摘要
- 下一篇: Kafka日志清理之Log Deleti