HBase_HBase的Rowkey协议与获取紧接着的字典序更大的Byte数组
1.HBase的Rowkey协议设计
1.1 存储字段
Rowkey:
1.不存在scan场景
MD5散列|时间|时间粒度|REALTIME/OFFLINE|分段(0)/累计(1)|维度id1,维度id2,维度id3|维值1,维值2,维值3
2.存在scan场景
比如维度2是scan维度字段
MD5散列|时间|时间粒度|REALTIME/OFFLINE|分段(0)/累计(1)|维度id1,维度id2,维度id3|维值1,维值3,维值2
MD5散列|时间|时间粒度|REALTIME/OFFLINE|分段(0)/累计(1)|cateid,skuid,shopid|2232,10026,10000768934
3.分时聚合粒度场景
分时字段格式为hh:mm
MD5散列|时间|时间粒度|REALTIME/OFFLINE|分段(0)/累计(1)|维度id1,维度id2|维值1,维值2,分时值
MD5散列|时间|时间粒度|REALTIME/OFFLINE|分段(0)/累计(1)|cateid,skuid|2232,10000768934,10:30
family:d
column:指标名称
value:指标值(全部转化为String类型存储)
1.2 Rowkey字段含义
MD5散列:选取使用md5对scan字段或分时字段以前的rowkey字符串进行散列然后取前10位。
1 | def getHashValue(value: String): String = { |
时间粒度:预聚合的时间粒度,枚举值BY_SECOND,BY_ONE_MIN,BY_TEN_MIN,BY_HOUR,BY_HALF_HOUR,BY_DAY,BY_WEEK,BY_MONTH,BY_SEASON,BY_YEAR,BY_WEEK_ACCU,BY_MONTH_ACCU,BY_SEASON_ACCU,BY_YEAR_ACCU,BY_LAST_DAY_7,BY_LAST_DAY_30
实时离线:实时还是离线,枚举值 REALTIME/OFFLINE
分时类型:分时还是累计,枚举值 分段(0)/累计(1)
维度组合:维度组合使用元数据中心unify_drive_physic_table.order_by字段 (scan场景,只是把scan字段放最后,并保存在order_by字段中。)(scan场景通过rowkey处理)
2.获取紧接着的字典序更大的Byte数组
2.1 简介
如第一节中Rowkey设计协议,在数据产品的HBase服务中,一般使用聚合维度组合的维值组成rowkey,如按一、二、三级品类进行聚合计算得到总成交金额:
| Cate_1 | Cate_2 | Cate_3 | Ord_amt |
|---|---|---|---|
| 1001 | 13564 | 163857 | 654.98 |
| 1001 | 13564 | 163864 | 37457.76 |
| 1001 | 13564 | 163452 | 6357.66 |
那么会用一、二、三级品类编码值组成一个定长的rowkey,对应品类的总成交金额作为value进行存储,如上即存储三个kv。在数据服务中,常有这种取数需求:知道一、二级品类编码值1001和13564,不知道具体三级品类编码值,希望直接获取到该一、二级品类下所有三级品类的成交金额榜单。
这时我们需要使用到HBase的scan接口,设置startkey为一、二级品类编码值拼接值1001,13564的Byte[],然后获取到与该startkey等长的紧接着的字典序更大的Byte数组作为endkey,即可实现取数目的。
如下方法即可实现该目的:
1 | public static byte[] getNextByte(byte [] arr){ |
1)使用Arrays.copyOf方法创建原数组的一个副本arr_next,确保操作不会修改原数组。
2)方法从数组的末尾开始向前遍历,这是因为我们需要找到最右边(低位)可以增加的位置,以保证变化最小且确保得到的数组在字典序上是“下一个”。
3)由于在Java中byte类型是有符号的,范围是-128到127,当某个元素的值是-1时(相当于所有位都为1的二进制数),该方法将其置为0(所有位都是0的二进制数),这是因为-1之后的值在字节中是最小值0。然后继续向前遍历数组的前一个元素。
4)如果找到的元素不是-1,该方法将此元素加1,然后中断循环。(byte)(arr_next[len]+1)这里确保即便结果的值超过了byte能表示的最大值127,也会通过类型转换(溢出)正确处理,例如128会变成-128(Java中byte的溢出行为)。
2.2 Java等编程语言中使用补码表示正负数
要理解上述getNextByte()方法最关键的一点就是理解,字典序的大小比较与十进制数的大小比较是不一样的。十进制数大小比较是比较我们常规意识上的数值大小,如一个byte能表示-128到127,其中-128最小,127最大。字典序大小是比较二进制数大小,如一个byte最小是00000000,也就是十进制0,最大是11111111,也就是十进制-1。
对于正数和零来说,补码的表示与其原码直接按位表示的码相同,也就是一个正整数或零在内存中的表示直接反映了它的真实值,例如给定一个8位的byte类型:
1)数值0的二进制原码表示为00000000,其补码表示(也就是它在计算机内存中的存储方式)也是00000000;
2)数值1的二进制原码表示为00000001,其补码表示也是00000001;
3)数值127(byte类型能表示的最大正整数)的二进制原码表示为01111111,其补码表示也是01111111。
对于负数,补码则是通过取其正值的原码,先进行按位取反,然后加上1来得到的。例如,-1的补码在8位系统中为11111111,这是因为1的原码00000001按位取反后为11111110,再加上1得到11111111。
所以getNextByte()方法中的arr_next[len] == -1就表示arr_next[len]达到了byte按字典序的最大值11111111。
2.3 计算机使用补码的优点
计算机使用补码(Two’s complement)表示正负数主要有以下优点:
1)负数的表示:补码系统中负数的表示非常优雅,负数-X的补码是将X的二进制表示按位取反然后加1。这种表示方法使得+0和-0有相同的表示(000...0);并且最大范围内的所有正数和负数可以无歧义地用相同位数表示。比如,在8位表示中,-1用11111111表示,-128用10000000表示。
2)自动处理溢出:当执行加法时,如果结果超出了表示范围,补码自动处理溢出,不需要额外的逻辑来检测或纠正这种情况,这意味着硬件可以更简单,而且运算结果仍然是正确的补码表示。
3)统一的加法器:由于补码表示的优点,加法器如全加器可以同时处理正数加法、正数与负数加法、负数加法,甚至减法(通过加上一个数的补码来执行),这极大地简化了计算机的算术逻辑单元(ALU)的设计。举个例子,假设要在8位系统中计算5 - 3。在二进制中,5表示为00000101。3的补码,也就是-3, 表示为11111101(先取3的二进制表示00000011,然后按位取反得到11111100,最后加1得到11111101)。当你在补码系统中把00000101(5)和11111101(-3)相加,结果是00000010(2),这正是5-3的结果。
由于这些原因,补码是完成整数算术运算的首选方法,尤其是在需要简化硬件设计并提高运算效率的环境中。