HBase中加盐(Salting)之后的表如何读取:协处理器文章
我们介绍了避免数据斑点的三种比较常见方法:
- 加盐-盐腌
- 哈希-散列
- 反转-反转
其中在加盐(Salting)的方法里面是这么描述的:给Rowkey分配一个随机指针以使其和之前排序不同。但是在Rowkey前面加了随机重叠,那么我们怎么将这些数据替换来呢?我将分三篇文章来介绍如何读取加盐之后的表,其中每篇文章提供一种方法,主要包括:
- 使用协处理器读取加盐的表
- 使用Spark读取加盐的表
- 使用MapReduce读取加盐的表
关于协处理器的入门及实战,参见请这里。本文使用的各组件版本:Hadoop的2.7.7,HBase的-2.0.4,jdk1.8.0_201。
测试数据生成
在介绍如何查询数据之前,我们先创建一张名为iteblog的HBase表,进行测试。为了数据均匀和介绍的方便,这里使用了预分区,并设置了27个分区,如下:
hbase(main):002:0> create 'iteblog' , 'f' , SPLITS => [ 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' , 'J' , 'K' , 'L' , 'M' , 'N' , 'O' , 'P' , 'Q' , 'R' , 'S' , 'T' , 'U' , 'V' , 'W' , 'X' , 'Y' , 'Z' ] 0 row(s) in 2.4880 seconds |
然后我们使用以下方法生成了1000000条测试数据。RowKey的形式为UID +当前数据生成版本;由于UID的长度为4,所以1000000条数据会存在大量的UID一样的数据,所以我们使用加盐方法将这些数据均匀分散到上述27个Region里面(注意,其实第一个Region其实没数据)。具体代码如下:
package com.iteblog.data; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.*; import org.apache.hadoop.hbase.util.Bytes; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.UUID; public class HBaseDataGenerator { private static byte [] FAMILY = "f" .getBytes(); private static byte [] QUALIFIER_UUID = "uuid" .getBytes(); private static byte [] QUALIFIER_AGE = "age" .getBytes(); private static char generateLetter() { return ( char ) (Math.random() * 26 + 'A' ); } private static long generateUid( int n) { return ( long ) (Math.random() * 9 * Math.pow( 10 , n - 1 )) + ( long ) Math.pow( 10 , n - 1 ); } public static void main(String[] args) throws IOException { BufferedMutatorParams bmp = new BufferedMutatorParams(TableName.valueOf( "iteblog" )); bmp.writeBufferSize( 1024 * 1024 * 24 ); Configuration conf = HBaseConfiguration.create(); Connection connection = ConnectionFactory.createConnection(conf); BufferedMutator bufferedMutator = connection.getBufferedMutator(bmp); int BATCH_SIZE = 1000 ; int COUNTS = 1000000 ; int count = 0 ; List<Put> putList = new ArrayList<>(); for ( int i = 0 ; i < COUNTS; i++) { String rowKey = generateLetter() + "-" + generateUid( 4 ) + "-" + System.currentTimeMillis(); Put put = new Put(Bytes.toBytes(rowKey)); byte [] uuidBytes = UUID.randomUUID().toString().substring( 0 , 23 ).getBytes(); put.addColumn(FAMILY, QUALIFIER_UUID, uuidBytes); put.addColumn(FAMILY, QUALIFIER_AGE, Bytes.toBytes( "" + new Random().nextInt( 100 ))); putList.add(put); count++; if (count % BATCH_SIZE == 0 ) { bufferedMutator.mutate(putList); bufferedMutator.flush(); putList.clear(); System.out.println(count); } } if (putList.size() > 0 ) { bufferedMutator.mutate(putList); bufferedMutator.flush(); putList.clear(); } } } |
运行完上面的代码之后,会生成1000000条数据(注意,这里其实不严谨,因为Rowkey设计问题,可能会导致重复的Rowkey生成,所以实际情况下可能没有1000000条数据。)。我们limit 10条数据看下长成什么样:
hbase(main):001:0> scan 'iteblog' , { 'LIMIT' =>10} ROW COLUMN+CELL A-1000-1550572395399 column=f:age, timestamp=1549091990253, value=54 A-1000-1550572395399 column=f:uuid, timestamp=1549091990253, value=e9b10a9f-1218-43fd-bd01 A-1000-1550572413799 column=f:age, timestamp=1549092008575, value=4 A-1000-1550572413799 column=f:uuid, timestamp=1549092008575, value=181aa91e-5f1d-454c-959c A-1000-1550572414761 column=f:age, timestamp=1549092009531, value=33 A-1000-1550572414761 column=f:uuid, timestamp=1549092009531, value=19aad8d3-621a-473c-8f9f A-1001-1550572394570 column=f:age, timestamp=1549091989341, value=64 A-1001-1550572394570 column=f:uuid, timestamp=1549091989341, value=c6712a0d-3793-46d5-865b A-1001-1550572405337 column=f:age, timestamp=1549092000108, value=96 A-1001-1550572405337 column=f:uuid, timestamp=1549092000108, value=4bf05d10-bb4d-43e3-9957 A-1001-1550572419688 column=f:age, timestamp=1549092014458, value=8 A-1001-1550572419688 column=f:uuid, timestamp=1549092014458, value=f04ba835-d8ac-49a3-8f96 A-1002-1550572424041 column=f:age, timestamp=1549092018816, value=84 A-1002-1550572424041 column=f:uuid, timestamp=1549092018816, value=99d6c989-afb5-4101-9d95 A-1003-1550572431830 column=f:age, timestamp=1549092026605, value=21 A-1003-1550572431830 column=f:uuid, timestamp=1549092026605, value=8c1ff1b6-b97c-4059-9b68 A-1004-1550572395399 column=f:age, timestamp=1549091990253, value=2 A-1004-1550572395399 column=f:uuid, timestamp=1549091990253, value=e240aa0f-c044-452f-89c0 A-1004-1550572403783 column=f:age, timestamp=1549091998555, value=6 A-1004-1550572403783 column=f:uuid, timestamp=1549091998555, value=e8df15c9-02fa-458e-bd0c 10 row(s) Took 0.1104 seconds |
使用协处理器查询加盐之后的表
现在有数据了,我们需要查询所有UID = 1000的用户所有历史数据,那么如何查呢?我们知道UID = 1000的用户数据是均匀放到上述的27个地区里面的,因为经过加盐了,所以这些数据垂直都是垂直A-,B-,C-
等开头的。其次我们需要知道,每个区域其实是有Start Key和End Key的,这些Start Key和End Key其实就是我们创建iteblog表指定的。如果你看了《 HBase协处理器入门及实战》这篇文章,你就知道协处理器的代码其实是在每个区域里面执行的;而这些代码在区域里面执行的时候是可以拿到当前Region的信息,包括了键和结束键,所以实际上我们可以将拿到的开始键信息和查询的UID进行拆分,这样就可以查询我们要的数据。协处理器处理文章就是基于这样的思想来查询加盐之后的数据的。
定义proto文件
为什么需要定义这个请参见《 HBase协处理器入门及实战》这篇文章。因为我们查询的时候需要引用查询的参数,表名,StartKey,EndKey以及是否加盐等标记;同时当查询到结果的当时,我们还需要将数据返回,所以我们定义的proto文件如下:
option java_package = "com.iteblog.data.coprocessor.generated" ; option java_outer_classname = "DataQueryProtos" ; option java_generic_services = true ; option java_generate_equals_and_hash = true ; option optimize_for = SPEED; message DataQueryRequest { optional string tableName = 1; optional string startRow = 2; optional string endRow = 3; optional bool incluedEnd = 4; optional bool isSalting = 5; } message DataQueryResponse { message Cell{ required bytes value = 1; required bytes family = 2; required bytes qualifier = 3; required bytes row = 4; required int64 timestamp = 5; } message Row{ optional bytes rowKey = 1; repeated Cell cellList = 2; } repeated Row rowList = 1; } service QueryDataService{ rpc queryByStartRowAndEndRow(DataQueryRequest) returns (DataQueryResponse); } |
我们然后使用protobuf-maven-plugin
插件将上面的原生成的Java类,具体如何操作参见“在IDEA中使用Maven的编译原文件”。将我们的生成DataQueryProtos.java
类拷贝产品到com.iteblog.data.coprocessor.generated
包里面。
编写协处理器代码
有了请求和返回的类,现在我们需要编写协处理器的处理代码了,结合上面的分析,协处理器的代码实现如下:
package com.iteblog.data.coprocessor; import com.google.protobuf.ByteString; import com.google.protobuf.RpcCallback; import com.google.protobuf.RpcController; import com.google.protobuf.Service; import com.iteblog.data.coprocessor.generated.DataQueryProtos.QueryDataService; import com.iteblog.data.coprocessor.generated.DataQueryProtos.DataQueryRequest; import com.iteblog.data.coprocessor.generated.DataQueryProtos.DataQueryResponse; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CoprocessorEnvironment; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.coprocessor.CoprocessorException; import org.apache.hadoop.hbase.coprocessor.RegionCoprocessor; import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; import org.apache.hadoop.hbase.regionserver.InternalScanner; import org.apache.hadoop.hbase.shaded.protobuf.ResponseConverter; import org.apache.hadoop.hbase.util.Bytes; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class SlatTableDataSearch extends QueryDataService implements RegionCoprocessor { private RegionCoprocessorEnvironment env; public Iterable<Service> getServices() { return Collections.singleton( this ); } @Override public void queryByStartRowAndEndRow(RpcController controller, DataQueryRequest request, RpcCallback<DataQueryResponse> done) { DataQueryResponse response = null ; String startRow = request.getStartRow(); String endRow = request.getEndRow(); String regionStartKey = Bytes.toString( this .env.getRegion().getRegionInfo().getStartKey()); if (request.getIsSalting()) { String startSalt = null ; if ( null != regionStartKey && regionStartKey.length() != 0 ) { startSalt = regionStartKey; } if ( null != startSalt && null != startRow) { startRow = startSalt + "-" + startRow; endRow = startSalt + "-" + endRow; } } Scan scan = new Scan(); if ( null != startRow) { scan.withStartRow(Bytes.toBytes(startRow)); } if ( null != endRow) { scan.withStopRow(Bytes.toBytes(endRow), request.getIncluedEnd()); } try (InternalScanner scanner = this .env.getRegion().getScanner(scan)) { List<Cell> results = new ArrayList<>(); boolean hasMore; DataQueryResponse.Builder responseBuilder = DataQueryResponse.newBuilder(); do { hasMore = scanner.next(results); DataQueryResponse.Row.Builder rowBuilder = DataQueryResponse.Row.newBuilder(); if (results.size() > 0 ) { Cell cell = results.get( 0 ); rowBuilder.setRowKey(ByteString.copyFrom(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength())); for (Cell kv : results) { buildCell(rowBuilder, kv); } } responseBuilder.addRowList(rowBuilder); results.clear(); } while (hasMore); response = responseBuilder.build(); } catch (IOException e) { ResponseConverter.setControllerException(controller, e); } done.run(response); } private void buildCell(DataQueryResponse.Row.Builder rowBuilder, Cell kv) { DataQueryResponse.Cell.Builder cellBuilder = DataQueryResponse.Cell.newBuilder(); cellBuilder.setFamily(ByteString.copyFrom(kv.getFamilyArray(), kv.getFamilyOffset(), kv.getFamilyLength())); cellBuilder.setQualifier(ByteString.copyFrom(kv.getQualifierArray(), kv.getQualifierOffset(), kv.getQualifierLength())); cellBuilder.setRow(ByteString.copyFrom(kv.getRowArray(), kv.getRowOffset(), kv.getRowLength())); cellBuilder.setValue(ByteString.copyFrom(kv.getValueArray(), kv.getValueOffset(), kv.getValueLength())); cellBuilder.setTimestamp(kv.getTimestamp()); rowBuilder.addCellList(cellBuilder); } /** * Stores a reference to the coprocessor environment provided by the * {@link org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost} from the region where this * coprocessor is loaded. Since this is a coprocessor endpoint, it always expects to be loaded * on a table region, so always expects this to be an instance of * {@link RegionCoprocessorEnvironment}. * * @param env the environment provided by the coprocessor host * @throws IOException if the provided environment is not an instance of * {@code RegionCoprocessorEnvironment} */ @Override public void start(CoprocessorEnvironment env) throws IOException { if (env instanceof RegionCoprocessorEnvironment) { this .env = (RegionCoprocessorEnvironment) env; } else { throw new CoprocessorException( "Must be loaded on a table region!" ); } } @Override public void stop(CoprocessorEnvironment env) { // nothing to do } } |
大家可以看到,这里面的代码框架和《 HBase协处理器入门及实战》里面介绍的HBase提供的RowCountEndpoint
示例代码很类似。主要逻辑在queryByStartRowAndEndRow
函数实现里面。我们通过DataQueryRequest
拿到客户端查询的表,StartKey和EndKey等数据。通过this.env.getRegion().getRegionInfo().getStartKey()
可以拿到当前区域的StartKey,然后再和和客户端传进来的StartKey和EndKey进行拼接就可以拿到完整的Rowkey插入。剩下的查询就是正常的HBase扫描代码了。
现在我们将SlatTableDataSearch
类进行编译打包,并部署到HBase表里面去,具体如何部署参见《 HBase协处理器入门及实战》
协处理器客户端代码编写
到这里,我们的协处理器服务器端的代码和部署已经完成了,现在我们需要编写协处理器客户端代码。其实也很简单,如下:
package com.iteblog.data; import com.iteblog.data.coprocessor.generated.DataQueryProtos.QueryDataService; import com.iteblog.data.coprocessor.generated.DataQueryProtos.DataQueryRequest; import com.iteblog.data.coprocessor.generated.DataQueryProtos.DataQueryResponse; import com.iteblog.data.coprocessor.generated.DataQueryProtos.DataQueryResponse.*; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.ConnectionFactory; import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.ipc.CoprocessorRpcUtils.BlockingRpcCallback; import org.apache.hadoop.hbase.ipc.ServerRpcController; import java.util.LinkedList; import java.util.List; import java.util.Map; public class DataQuery { private static Configuration conf = null ; static { conf = HBaseConfiguration.create(); } static List<Row> queryByStartRowAndStopRow(String tableName, String startRow, String stopRow, boolean isIncludeEnd, boolean isSalting) { final DataQueryRequest.Builder requestBuilder = DataQueryRequest.newBuilder(); requestBuilder.setTableName(tableName); requestBuilder.setStartRow(startRow); requestBuilder.setEndRow(stopRow); requestBuilder.setIncluedEnd(isIncludeEnd); requestBuilder.setIsSalting(isSalting); try { Connection connection = ConnectionFactory.createConnection(conf); HTable table = (HTable) connection.getTable(TableName.valueOf(tableName)); Map< byte [], List<Row>> result = table.coprocessorService(QueryDataService. class , null , null , counter -> { ServerRpcController controller = new ServerRpcController(); BlockingRpcCallback<DataQueryResponse> call = new BlockingRpcCallback<>(); counter.queryByStartRowAndEndRow(controller, requestBuilder.build(), call); DataQueryResponse response = call.get(); if (controller.failedOnException()) { throw controller.getFailedOn(); } return response.getRowListList(); }); List<Row> list = new LinkedList<>(); for (Map.Entry< byte [], List<Row>> entry : result.entrySet()) { if ( null != entry.getKey()) { list.addAll(entry.getValue()); } } return list; } catch (Throwable e) { e.printStackTrace(); } return null ; } public static void main(String[] args) { List<Row> rows = queryByStartRowAndStopRow( "iteblog" , "1000" , "1001" , false , true ); if ( null != rows) { System.out.println(rows.size()); for (DataQueryResponse.Row row : rows) { List<DataQueryResponse.Cell> cellListList = row.getCellListList(); for (DataQueryResponse.Cell cell : cellListList) { System.out.println(row.getRowKey().toStringUtf8() + " \t " + "column=" + cell.getFamily().toStringUtf8() + ":" + cell.getQualifier().toStringUtf8() + ", " + "timestamp=" + cell.getTimestamp() + ", " + "value=" + cell.getValue().toStringUtf8()); } } } } } |
我们运行上面的代码,可以得到如下的输出:
A-1000-1550572395399 column=f:age, timestamp=1549091990253, value=54 A-1000-1550572395399 column=f:uuid, timestamp=1549091990253, value=e9b10a9f-1218-43fd-bd01 A-1000-1550572413799 column=f:age, timestamp=1549092008575, value=4 A-1000-1550572413799 column=f:uuid, timestamp=1549092008575, value=181aa91e-5f1d-454c-959c A-1000-1550572414761 column=f:age, timestamp=1549092009531, value=33 A-1000-1550572414761 column=f:uuid, timestamp=1549092009531, value=19aad8d3-621a-473c-8f9f B-1000-1550572388491 column=f:age, timestamp=1549091983276, value=1 B-1000-1550572388491 column=f:uuid, timestamp=1549091983276, value=cf720efe-2ad2-48d6-81b8 B-1000-1550572392922 column=f:age, timestamp=1549091987701, value=7 B-1000-1550572392922 column=f:uuid, timestamp=1549091987701, value=8a047118-e130-48cb-adfe hbase(main):020:0> scan 'iteblog' , {STARTROW => 'A-1000' , ENDROW => 'A-1001' } ROW COLUMN+CELL A-1000-1550572395399 column=f:age, timestamp=1549091990253, value=54 A-1000-1550572395399 column=f:uuid, timestamp=1549091990253, value=e9b10a9f-1218-43fd-bd01 A-1000-1550572413799 column=f:age, timestamp=1549092008575, value=4 A-1000-1550572413799 column=f:uuid, timestamp=1549092008575, value=181aa91e-5f1d-454c-959c A-1000-1550572414761 column=f:age, timestamp=1549092009531, value=33 A-1000-1550572414761 column=f:uuid, timestamp=1549092009531, value=19aad8d3-621a-473c-8f9f 3 row(s) Took 0.0569 seconds |
可以看到,和我们使用HBase Shell输出的一致,而且我们还把所有的UID = 1000的数据拿到了。好了,到这里,使用协处理器查询HBase加盐之后的表已经算完成了,明天我将介绍使用Spark如何查询加盐之后的表。
HBase中加盐(Salting)之后的表如何读取:协处理器文章的更多相关文章
- HBase 中加盐之后的表如何读取:Spark 篇
在 <HBase 中加盐之后的表如何读取:协处理器篇> 文章中介绍了使用协处理器来查询加盐之后的表,本文将介绍第二种方法来实现相同的功能. 我们知道,HBase 为我们提供了 hbase- ...
- HBase 中加盐(Salting)之后的表如何读取:Spark 篇
我们知道,HBase 为我们提供了 hbase-mapreduce 工程包含了读取 HBase 表的 InputFormat.OutputFormat 等类.这个工程的描述如下:This module ...
- hbase数据加盐(Salting)存储与协处理器查询数据的方法
转自: https://blog.csdn.net/finad01/article/details/45952781 ----------------------------------------- ...
- MD5加密算法中的加盐值 ,和彩虹表攻击 防止彩虹表撞库
一.什么是彩虹表? 彩虹表(Rainbow Tables)就是一个庞大的.针对各种可能的字母组合预先计算好的哈希值的集合,不一定是针对MD5算法的,各种算法的都有,有了它可以快速的破解各类密码.越是复 ...
- hive和hbase本质区别——hbase本质是OLTP的nosql DB,而hive是OLAP 底层是hdfs,需从已有数据库同步数据到hdfs;hive可以用hbase中的数据,通过hive表映射到hbase表
对于hbase当前noSql数据库的一种,最常见的应用场景就是采集的网页数据的存储,由于是key-value型数据库,可以再扩展到各种key-value应用场景,如日志信息的存储,对于内容信息不需要完 ...
- abp架构中加载公共css样式表和公共js的文件目录位置
src\shared\helpers\LocalizedResourcesHelper.ts
- 将HBase中的表加载到hive中
两种方式加载hbase中的表到hive中,一是hive创建外部表关联hbase表数据,二是hive创建普通表将hbase的数据加载到本地 1. 创建外部表 hbase中已经有了一个test表,内容如下 ...
- [Phoenix] 四、加盐表
摘要: 在密码学中,加盐是指在散列之前将散列内容(例如:密码)的任意固定位置插入特定的字符串.这个在散列中加入字符串的方式称为“加盐”.其作用是让加盐后的散列结果和没有加盐的结果不相同,在不同的应用情 ...
- Spark读取Hbase中的数据
大家可能都知道很熟悉Spark的两种常见的数据读取方式(存放到RDD中):(1).调用parallelize函数直接从集合中获取数据,并存入RDD中:Java版本如下: JavaRDD<Inte ...
随机推荐
- SVN强制添加备注
1.进入仓库project1/hooks目录,找到pre-commit.tmpl文件 cp pre-commit.tmpl pre-commit 2.编辑pre-commit文件, 将: $SVNLO ...
- Git-Jenkins-代码的上线
第一章:自动化上线代码基本介绍 1.软件开发生命周期 老板的创意---产品经理---立项---开发团队---测试团队---运维上线 产品经理---加需求---开发团队---测试----更新代码,上线 ...
- HTML开发实例-简单相亲网站开发(主体为table)
实现功能:简单的相亲网站: 清楚不常在,抓紧谈恋爱 我承诺 年满十八岁 单身 抱着严肃态度 寻找真诚的另一半 性别: 男 女 生日: --请选择年-- 2019 2020 2021 --请选择月-- ...
- pop() 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。
pop() 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值.
- sqlmap基本使用
sqlmap 使用 需要安装python2的环境 使用方法: 用最基础的get型注入开始 1.当我们发现注入点的时候, python sqlmap.py -u "http://192.168 ...
- group by和having注意事项
执行和编写顺序:join->where->group by->having 其中where与having的区别: where用于在group by分组之前过滤掉某些行, group ...
- java内部类简单用法
package innerClass; /** * 特点 * 1:增强封装性,通过把内部类隐藏在外部类的里面,使得其他类不能访问外部类. * 2:增强可维护性. * 3:内部类可以访问外部的成员. * ...
- 微软:悬赏10万美金破解 Linux 系统
微软选择了 Linux 系统作为物联网平台,并且悬赏10万美金邀请黑客来进行破解. 当然,该悬赏计划不是针对所有的 Linux 系统,而是特别针对微软的物联网端对端安全平台Azure Sphere.本 ...
- AES实现财务数据的加密解密存储
需求背景 众所周知,金融行业有各种各样的财务报表,有些报表涉及到公司财务或经营相关的敏感数据,需要进行加密存储,只有掌握密钥的用户才能看到解密后的数据.注意,这里所说的加密并不是针对整个数据库或者表全 ...
- Web-从Java Request对象到HTTP协议
https://mp.weixin.qq.com/s/PjcA22STEDGwRxVQweObQQ Java Web中的Request对象是哪里来的?Response对象的角色是什么? Java We ...