学习目标 ● 了解过滤器原理,能够说出基于过滤器查询数据的流程。 ● 掌握过滤器的使用,能够灵活运用不同类型的过滤器查询数据。 过滤器(Filter)是一种在HBase中用于查询和筛选数据的工具。它可以根据行键、列 族、列标识等信息快速过滤掉表中不符合特定条件的数据,从而提高查询性能和减少数据传 输。HBase提供了多种类型的过滤器,如值过滤器、行过滤器等,用户可以根据实际需求选 择适合的过滤器类型来指定过滤条件。在HBase中,可以通过HBaseJavaAPI或HBase Shel 使用过滤器。本章重点介绍使用HBaseJavaAPI来使用过滤器的方法。 5.过滤器原理 1 过滤器的实现原理是,当客户端基于过滤器读取HBase的数据时,HBase只会将表中 符合过滤器指定条件的数据返回给客户端,这样可以避免不符合过滤器指定条件的数据被 传输到客户端,减轻网络传输和客户端的压力。 那么过滤器是如何避免不符合过滤器条件的数据被传输到客户端的呢? 这主要是因为 HBase采用谓词下推(predicatepushdown)的规则执行过滤器的操作,所谓谓词下推是指 将过滤器尽可能下推到距离数据源最近的地方执行,以尽早完成数据的过滤。同样地,在学 习和工作中,人们可以借鉴谓词下推的原则,将注意力集中在关键的事物上,能够更好地管 理时间和资源,取得更好的成果。由于在HBase中,数据是由RegionServer进行管理的,所 以过滤器会被HBase传输到RegionServer执行,此时只有符合过滤器条件的数据才会被传 输到客户端。例如,客户端基于过滤器执行scan命令查询HBase表数据时的执行流程如 图5-1所示。 在图5-1中,过滤器在客户端(Client)执行scan命令时被创建,创建完成后会被序列化 为网络传输的格式,然后通过RPC协议分发到HBase集群中管理相关表数据的 RegionServer,在RegionServer中过滤器会通过反序列化被还原,并且在RegionScanner中 基于过滤器进行查询数据的操作,最终将符合过滤器条件的数据返回给客户端。 在客户端查询数据时,常用的操作之一是通过过滤器扫描表。扫描表是指使用HBase Shel 提供的scan命令或HBaseJavaAPI中Table类的getScanner()方法来查询数据。本 章的后续内容将重点介绍如何使用HBaseJavaAPI在Java应用程序中通过过滤器进行扫 描表。 HBase基1 44 础入门 图5-1 过滤器的执行流程 Scan类提供了setFilter()方法用于为扫描表设置过滤器,其程序结构如下。 scan.setFilter(Filter); 上述程序结构中,scan为Scan类的实例,Filter用于指定设置的过滤器。 5.2 环境准备 在进行某项事务前,充足的准备能够使我们更好地发挥自己的潜力,提高自身能力和素 质。这包括深入学习相关知识,积累相关经验,以及适时地进行规划。通过充分准备,能够 为自己创造更多机会,增加成功的可能性,并提高对事务的把控能力和执行效果。 本章后续的内容主要通过在IntelliJIDEA 实现Java应用程序,演示如何基于过滤器查 图5-2 项目构建完成的效果 询数据,为了后续内容讲解的便利,这里事先对相关 环境进行准备,包括构建Java项目、导入依赖、启动 集群环境、连接HBase、创建命名空间、创建表,以及 向表插入数据等,具体操作步骤如下。 1.构建Java项目 在IntelliJIDEA基于Maven构建Java项目HBase _Chapter05,并且创建包cn.itcast.hbasedemo.connect和 cn.itcast.hbasedemo.filter,前者用于存放连接HBase相 关操作的Java文件,后者用于存放过滤器相关操作的 Java文件,项目构建完成的效果如图5-2所示。 2.导入依赖 在Java项目的pom.xml文件中添加HBase的客户端依赖,依赖添加完成的效果如文 件5-1所示。 文件5-1 pom.xml 1 2 6 4.0.0 7 cn.itcast 8 HBase_Chapter03 9 1.0-SNAPSHOT 10 11 8 12 8 13 14 15 //HBase 的客户端依赖 16 17 org.apache.hbase 18 hbase-shaded-client 19 2.0.0 20 21 22 3.启动集群环境 在虚拟机HBase01、HBase02和HBase03按照ZooKeeper、Hadoop和HBase的顺序启 动相关集群,本章使用基于完全分布式模式部署的HBase集群。 4.连接HBase 在Java项目的cn.itcast.hbasedemo.connect包中创建HBaseConnect类,该类用于实 现连接HBase的功能,如文件5-2所示。 文件5-2 HBaseConnect.java 1 public class HBaseConnect { 2 public static Connection getConnect() { 3 Connection conn =null; 4 try { 5 Configuration config =HBaseConfiguration.create(); 6 config.set("hbase.zookeeper.quorum","hbase01,hbase02,hbase03"); 7 config.set("zookeeper.znode.parent","/hbase-fully"); 8 conn =ConnectionFactory.createConnection(config); 9 } 10 catch (Exception e){ 11 e.printStackTrace(); 12 } 13 return conn; 14 } 15 public static void closeConn() { 16 Connection connect =getConnect(); 17 try { 18 if (connect.isClosed() ==false){ 19 System.out.println("关闭HBase 连接!!!!!!"); 20 connect.close(); 21 } 22 }catch (Exception e){ 23 e.printStackTrace(); HBase基1 46 础入门 24 } 25 } 26 } 文件5-2的内容与4.2节中文件4-2的内容一致,这里不再赘述。 5.创建命名空间 创建命名空间commodity,该命名空间用于存放本章相关操作的表。在HBaseShell 执行如下命令。 >create_namespace 'commodity' 6.创建表 在命名空间commodity中创建表fruit_table,并指定列族fruit_info和sale_info,其中 列族fruit_info用于存储水果信息,列族sale_info用于存放销售信息。在HBaseShell执行 如下命令。 >create 'commodity:fruit_table','fruit_info','sale_info' 7.向表插入数据 将数据文件fruit_info.csv存储的水果销售数据插入命名空间commodity的表fruit_ table,有关数据文件fruit_info.csv的内容如图5-3所示。 图5-3 数据文件fruit_info.csv的内容 在图5-3中,每行数据的字段按照从左到右的顺序依次表示水果编号、水果名称、水果 类型、水果产地、单价(单位是元/千克)、销售量(单位是千克)和总销售额(单位是元)。 在插入数据时,如果使用HBaseShell的方式实现,数字类型的数据(如总销售额和单 价)会被转换为字符串类型进行存储,这将导致后续无法使用过滤器基于数字类型的数据进 行过滤。因此,这里通过编写Java应用程序的方式实现插入数据的操作。 在Java项目的cn.itcast.hbasedemo.connect包中创建LoadCsvData类,该类用于将数据文 件fruit_info.csv的数据插入命名空间commodity的表fruit_table,具体代码如文件5-3所示。 第5章 HBase过滤器1 47 文件5-3 LoadCsvData.java 1 public class LoadCsvData { 2 private static Connection conn; 3 private static Table table; 4 public static void main(String[]args) throws IOException { 5 //连接HBase 6 conn =HBaseConnect.getConnect(); 7 //向命名空间commodity 的表fruit_table 插入数据 8 table =conn.getTable(TableName.valueOf("commodity:fruit_table")); 9 //指定数据文件fruit_info.csv 的存储路径 10 String path ="D:\\Data\\fruit_info.csv"; 11 //水果编号 12 String fruitNo =""; 13 //水果名称 14 String fruitName =""; 15 //水果类型 16 String fruitType =""; 17 //水果产地 18 String fruitOrigin =""; 19 //单价 20 BigDecimal unitPrice =null; 21 //销售量 22 Long quantity =0L; 23 //总销售额 24 BigDecimal totalSale =null; 25 //通过定义的readCsvByCsvReader()方法获取数据文件的数据 26 ArrayListcsvData =readCsvByCsvReader(path); 27 for (int i =0;i puts =new ArrayList<>(); 29 fruitNo =csvData.get(i)[0]; 30 fruitName =csvData.get(i)[1]; 31 fruitType =csvData.get(i)[2]; 32 fruitOrigin =csvData.get(i)[3]; 33 unitPrice =new BigDecimal(csvData.get(i)[4]); 34 quantity =Long.valueOf(csvData.get(i)[5]); 35 totalSale =new BigDecimal(csvData.get(i)[6]); 36 Put put =new Put(Bytes.toBytes(fruitNo)); 37 Put put1 =put.addColumn( 38 Bytes.toBytes("fruit_info"), 39 Bytes.toBytes("fruitName"), 40 Bytes.toBytes(fruitName)); 41 Put put2 =put.addColumn( 42 Bytes.toBytes("fruit_info"), 43 Bytes.toBytes("fruitType"), 44 Bytes.toBytes(fruitType)); 45 Put put3 =put.addColumn( 46 Bytes.toBytes("fruit_info"), 47 Bytes.toBytes("fruitOrigin"), 48 Bytes.toBytes(fruitOrigin)); 49 Put put4 =put.addColumn( 50 Bytes.toBytes("sale_info"), 51 Bytes.toBytes("unitPrice"), HBase基1 48 础入门 52 Bytes.toBytes(unitPrice)); 53 Put put5 =put.addColumn( 54 Bytes.toBytes("sale_info"), 55 Bytes.toBytes("quantity"), 56 Bytes.toBytes(quantity)); 57 Put put6 =put.addColumn( 58 Bytes.toBytes("sale_info"), 59 Bytes.toBytes("totalSale"), 60 Bytes.toBytes(totalSale)); 61 puts.add(put1); 62 puts.add(put2); 63 puts.add(put3); 64 puts.add(put4); 65 puts.add(put5); 66 puts.add(put6); 67 table.put(puts); 68 } 69 HBaseConnect.closeConn(); 70 } 71 public static ArrayListreadCsvByCsvReader(String filePath) { 72 ArrayListarrList =new ArrayList(); 73 try { 74 CsvReader reader =new CsvReader( 75 filePath, 76 ',', 77 Charset.forName("UTF-8")); 78 while (reader.readRecord()) { 79 arrList.add(reader.getValues()); 80 } 81 reader.close(); 82 } catch (Exception e) { 83 e.printStackTrace(); 84 } 85 return arrList; 86 } 87 } 在文件5-3中,第27~68行代码用于遍历集合,获取数据文件的每行数据。然后,从当 前行数据中提取水果编号、水果名称、水果类型、水果产地、单价、销售量和总销售额,并将它 们赋值给相应的变量。最后,使用水果编号作为行键,将水果名称、水果类型、水果产地、单 价、销售量和总销售额插入命名空间commodity的表fruit_table中相应的列。 第71~86行代码定义的readCsvByCsvReader()方法用于读取CSV 文件,并将文件中 的每行数据存储为集合csvData的元素。 8.验证数据是否插入成功 文件5-3运行完成后,执行“scan 'commodity:fruit_table',{LIMIT => 2}”命令查询 命名空间commodity中表fruit_table的前两行数据,如图5-4所示。 从图5-4可以看出,数据文件fruit_info.csv的数据成功插入命名空间commodity的表 fruit_table中。 需要说明的是,HBaseShell只能正常显示字符串类型的数据,而列sale_info: unitPrice、sale_info:quantity和sale_info:totalSale的数据是数字类型,因此以二进制形式 第5章 HBase过滤器1 49 图5-4 查询命名空间commodity中表fruit_table的前两行数据 进行显示。 5.3 值过滤器 值过滤器(ValueFilter)基于数据进行过滤。在使用值过滤器扫描表时,HBase会逐条 检查指定表中的数据,并将其与用户定义的条件进行比较。如果数据满足用户定义的条件, HBase会将该数据所在的单元格返回给客户端。 HBaseJavaAPI提供了ValueFilter类用于创建值过滤器,其程序结构如下。 ValueFilter valueFilter = new ValueFilter( compareoperator, comparator ); 上述程序结构中,valueFilter用于定义值过滤器的名称。compareoperator用于指定比 较关系,如大于、等于、小于等。comparator用于指定比较器,比较器根据比较关系来确定 比较的方式,例如比较字符串是否相等、比较数字的大小关系等。通过将比较关系和比较器 结合使用,可以形成完整的条件,用于值过滤器进行数据过滤。 接下来对HBaseJavaAPI常用的比较关系和比较器进行介绍,如表5-1 和表5-2 所示。 表5-1 HBaseJavaAPI常用的比较关系 比较关系描 述 CompareOperator.LESS 用于指定小于的比较关系 CompareOperator.LESS_OR_EQUAL 用于指定小于或等于的比较关系 CompareOperator.EQUAL 用于指定相等的比较关系 CompareOperator.NOT_EQUAL 用于指定不相等的比较关系 CompareOperator.GREATER_OR_EQUAL 用于指定大于或等于的比较关系 CompareOperator.GREATER 用于指定大于的比较关系 HBase基1 50 础入门 表5-2 HBaseJavaAPI常用的比较器 比 较 器描 述 newNullComparator() 用于比较数据是否等于null newBinaryComparator(value) 用于将行键、列族、数据等信息视为字节数组与字节数组value进行 比较,如搭配比较关系CompareOperator.EQUAL使用,比较数据是 否等于value newSubstringComparator(value) 用于将行键、列族、数据等信息视为字符串与字符串value进行比较, 如搭配比较关系CompareOperator.EQUAL 使用,比较列族是否等 于value newLongComparator(value) 将数据与Long 类型的数值value 进行比较,如搭配比较关系 CompareOperator.LESS使用,比较数据是否小于value newRegexStringComparator(value) 用于将行键、列族、数据等信息视为字符串与正则表达式value进行 匹配,如搭配比较关系CompareOperator.EQUAL使用,比较数据是 否匹配正则表达式 newBinaryPrefixComparator(value) 用于将行键、列族、数据等信息的前缀视为字节数组与字节数组 value进行比较,如搭配比较关系CompareOperator.EQUAL使用,比 较列族的前缀是否等于value newBigDecimalComparator(value) 用于将数据与BigDecimal类型的数值value进行比较,如搭配比较关 系CompareOperator.LESS使用,比较数据是否小于value 接下来演示如何在Java应用程序中使用值过滤器查询数据。在Java项目的cn.itcast. hbasedemo.filter包中创建ValueFilterDemo类,该类用于查询命名空间为commodity的表 fruit_table中水果产地为Hainan的水果编号,具体代码如文件5-4所示。 文件5-4 ValueFilterDemo.java 1 public class ValueFilterDemo { 2 private static Connection conn; 3 private static Table table; 4 public static void main(String[]args) throws IOException { 5 conn =HBaseConnect.getConnect(); 6 //指定查询命名空间commodity 中表fruit_table 的数据 7 table =conn.getTable(TableName.valueOf("commodity:fruit_table")); 8 Scan scan =new Scan(); 9 //指定查询列族fruit_info 的数据 10 scan.addFamily(Bytes.toBytes("fruit_info")); 11 //创建值过滤器 12 ValueFilter valueFilter =new ValueFilter( 13 CompareOperator.EQUAL, 14 new BinaryComparator(Bytes.toBytes("Hainan")) 15 ); 16 //设置值过滤器 17 scan.setFilter(valueFilter); 18 for (Result result : table.getScanner(scan)) { 19 List cells =result.listCells(); 20 for (Cell cell : cells) { 21 String rowkey =new String( 22 cell.getRowArray(), 第5章 HBase过滤器1 51 23 cell.getRowOffset(), 24 cell.getRowLength() 25 ); 26 System.out.println("水果编号:" +rowkey); 27 } 28 } 29 HBaseConnect.closeConn(); 30 } 31 } 在文件5-4中,第12~15行代码创建值过滤器valueFilter,该过滤器用于比较数据是 否等于Hainan。第18~28行代码用于查询数据并遍历查询结果,获取查询结果中每个单 元格的行键,即每个水果的水果编号。 文件5-4的运行结果如图5-5所示。 图5-5 文件5-4的运行结果 从图5-5可以看出,水果产地为Hainan的水果编号包括fruit005、fruit015和fruit017。 5.4 列值过滤器 列值过滤器(ColumnValueFilter)基于列进行过滤。在使用列值过滤器扫描表时, HBase会逐条检查指定列的数据,并将其与用户定义的条件进行比较。如果数据满足用户 定义的条件,HBase会将该数据所在的单元格返回给客户端。 HBaseJavaAPI提供了ColumnValueFilter类用于创建列值过滤器,其程序结构如下。 ColumnValueFilter columnValueFilter = new ColumnValueFilter( columnfamily, qualifier, compareoperator, comparator ); 上述程序结构中,columnValueFilter用于定义列值过滤器的名称,columnfamily用于 指定列族,qualifier用于指定列标识,compareoperator和comparator分别用于指定比较关 系和比较器。 接下来演示如何在Java应用程序中使用列值过滤器查询数据。在Java项目的cn. itcast.hbasedemo.filter包中创建ColumnValueFilterDemo类,该类用于查询命名空间为 commodity的表fruit_table中总销售额大于或等于5000的水果编号及其总销售额,具体代 码如文件5-5所示。 HBase基1 52 础入门 文件5-5 ColumnValueFilterDemo.java 1 public class ColumnValueFilterDemo { 2 private static Connection conn; 3 private static Table table; 4 public static void main(String[]args) throws IOException { 5 conn =HBaseConnect.getConnect(); 6 //指定查询命名空间commodity 中表fruit_table 的数据 7 table =conn.getTable(TableName.valueOf("commodity:fruit_table")); 8 Scan scan =new Scan(); 9 //创建列值过滤器 10 ColumnValueFilter columnValueFilter =new ColumnValueFilter( 11 Bytes.toBytes("sale_info"), 12 Bytes.toBytes("totalSale"), 13 CompareOperator.GREATER_OR_EQUAL, 14 new BigDecimalComparator(new BigDecimal(5000))); 15 //设置列值过滤器 16 scan.setFilter(columnValueFilter); 17 for (Result result : table.getScanner(scan)) { 18 List cells =result.listCells(); 19 for (Cell cell : cells) { 20 String rowkey =new String( 21 cell.getRowArray(), 22 cell.getRowOffset(), 23 cell.getRowLength() 24 ); 25 BigDecimal value =Bytes.toBigDecimal( 26 cell.getValueArray(), 27 cell.getValueOffset(), 28 cell.getValueLength() 29 ); 30 System.out.println("水果编号:" +rowkey 31 +"\t" +"总销售额:" +value); 32 } 33 } 34 HBaseConnect.closeConn(); 35 } 36 } 在文件5-5中,第10~14行代码创建列值过滤器columnValueFilter,该过滤器用于比 较列sale_info:totalSale的数据是否大于或等于5000。第17~33行代码用于查询数据并 遍历查询结果,获取查询结果中每个单元格的行键和数据,即每个水果的水果编号和总销 售额。文 件5-5的运行结果如图5-6所示。 从图5-6 可以看出,水果的总销售额大于或等于5000 的水果编号包括fruit005、 fruit006、fruit015、fruit016、fruit018和fruit020,它们的总销售额分别是7020、12012、6375、 6596、5147.5和8015。