菜单
本页目录

名称空间(namespace)

说明

  • 在一个项目中,需要使用HBase保存多张表,这些表会按照业务域来划分
  • 为了方便管理,不同的业务域以名称空间(namespace)来划分,这样管理起来会更加容易
  • 类似于Hive中的数据库,不同的数据库下可以放不同类型的表
  • HBase默认的名称空间是「default」,默认情况下,创建表时表都将创建在 default 名称空间下
  • HBase中还有一个命名空间「hbase」,用于存放系统的内建表(namespace、meta)

namespace default

namespace hbase

ruby 创建表语法

创建命名空间

1.创建一个命名空间
create_namespace 'MOMO_CHAT'
2.列出命名空间
list_namespace
3.删除命名空间
  • 注意:删除命名空间时,必续命名空间没有表
drop_namespace 'MOMO_CHAT'
4.查看命名空间
describe_namespace 'MOMO_CHAT'
5.创建表
  • 注意:表名必须带上命名空间,要不然表的命名空间默认为defult
create 'MOMO_CHAT:MSG', 'C1'

表设计

  • 列族:推荐1-2个,能使用1个就不是使用2个
  • 版本的设计:如果我们的项目不需要保存历史的版本,直接按照默认配置VERSIONS=1就OK。如果项目中需要
  • 保存历史的变更信息,就可以将VERSIONS设置为>1。但是设置为大于1也就意味着要占用更多的空间

列族设计

  • HBase列的数量应该越少越好
    • 两个及以上的列族HBase性能并不是很好
    • 一个列所存储的数据达到flush的闽值时,表中所有列族将同时进行flush操作。
    • 这将带来不必要的I/O开销,列族越多,对性能影响越大

版本设计

说明

  • 对于历史记录

    • 此处,我们需要保存的历史记录是不会更新的,一旦数据保存到HBase中,就不会再更新
    • 无需考虑版本问题
    • 只保留一个版本即可,这样可以节省大量空间
    • HBase默认创建表的版本为1,故此处保持默认可
  • 对于Hbase默认

    • 版本是相对与列族而言
    • 默认列族的版本为1
    hbase:005:0> describe 'MOMO_CHAT:MSG'
    Table MOMO_CHAT:MSG is ENABLED                                                                                                                                                                                
    MOMO_CHAT:MSG, {TABLE_ATTRIBUTES => {METADATA => {'hbase.store.file-tracker.impl' => 'DEFAULT'}}}                                                                                                             
    COLUMN FAMILIES DESCRIPTION                                                                                                                                                                                   
    {NAME => 'C1', INDEX_BLOCK_ENCODING => 'NONE', VERSIONS => '1', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 
    'ROW', IN_MEMORY => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536 B (64KB)'}                                                                                                      
    
    1 row(s)
    Quota is disabled
    Took 0.1626 seconds     
    
    • VERSIONS => '1' 就是默认版本只有一份

数据压缩

压缩算法

在HBase可以使用多种压缩编码,包括LZO、SNAPPY、GZIP。只在硬盘压缩,内存中或者网络传输中没有压缩。

压缩算法压缩后占比压缩速度解压缩速度
GZIP13.4%21 MB/s118 MB/s
LZO20.5%135 MB/s410 MB/s
Zippy/Snappy22.2%172 MB/s409 MB/s
  • GZIP的压缩率最高,但是其实CPU密集型的,对CPU的消耗比其他算法要多,压缩和解压速度慢
    • 适合需要高压缩率的场景,但需要考虑其对 CPU 的消耗。
  • LZO的压缩率居中,比GZIP要低些,但是压缩和解压速度明显要比GZIP很多,其中解压速度快的更多
    • 适合需要快速压缩和极快解压的场景,尤其在需要高吞吐量的应用中。
  • zippy/Snappy的压缩率最低,而压缩和解压速度要微比LZO要快一些
    • 适合对压缩率要求不高但追求速度的场景,特别是实时性高、低延迟要求的系统。

设置数据压缩

  • 创建新表,并指定数据压缩算法
    create 'MOMO_CHAT:MSG',{NAME => 'C1',COMPRESSION => 'GZ'}
    
  • 修改已有的表,并指定压缩算法
    • 默认是不用禁用表的,但是如何这个表是上线使用了
    • 禁用了再修改防止数据丢失
    disable 'MOMO_CHAT:MSG'
    alter 'MOMO_CHAT:MSG', {NAME => 'C1', COMPRESSION => 'GZ'}
    enable 'MOMO_CHAT:MSG'
    

ROWKEY设计原则

Hbase官方设计原则

  1. 避免使用递增行键/时序数据

    • 如果ROWKEY设计的都是按照顺序递增(例如:时间戳),这样会有很多的数据写入时,负载都在台机器上。我们尽量应当将写入大压力均衡到各个RegionServer
  2. 避免ROWKEY和列的长度过大

    • 在HBase中,要访问一个Cell(单元格),需要有ROWKEY、列、列名,如果ROWKEY、列名太大,就会占用较大内存空间。所以ROWKEY和列的长度应该尽量短小
    • ROWKEY的最大长度是64KB,建议越短越好,
  3. 使用long等类型比String类型更省空间

    • long类型为8个字节,8个字节可以保存非常大的无符号整数,例如:18446744073709551615。如果是字符串,是按照一个字节一个字符方式保存,需要快3倍的字节数存储。
  4. ROWKEY唯一性

    • 设计ROWKEY时,必须保证RowKey的唯一性
    • 由于在HBase中数据存储是key-Value形式,若向HBase中同一张表插入相同RowKey的数据则原先存在的数据会被新的数据覆盖。

避免热点设计

  • 热点是指大量的客户端(client)直接访问集群的一个或者几个节点(可能是读、也可能是写)
  • 大量地访问量可能会使得某个服务器节点超出承受能力,导致整个RegionServer的性能下降其他的Region也会受影响

预分区

  • 默认情况,一个HBase的表只有一个Region,被托管在一个RegionServer中

Start Key - End Key

  • 每个Region有两个重要的属性:Start Key、End Key,表示这个Region维护的ROWKEY范围
  • 如果只有一个Region,那么Start Key、End Key都是空的,没有边界。所有的数据都会放在这个Region中,但当数据越来越大时,会将Region分裂,取一个Mid key来分裂成两个Region -预分区个数=节点的倍数。默认Region的大小为10G,假设我们预估1年下来的大小为10T,则10000G/10G=1000个Region,所以,我们可以预设为1000个Region,这样,1000个Region将均衡地分布在各个节点上

ROWKEY避免热点设计

  1. 反转策略

    • 如果设计出的ROWKEY在数据分布上不均匀,但ROWKEY尾部的数据却呈现出了良好的随机性,可以考虑将ROWKEY的翻转,或者直接将尾部的bytes提前到ROWKEY的开头
    • 这种反转不一定是反转尾部,可以反转任意位置,但这个位置能确定是足够随机的
    • 反转策略可以使ROWKEY随机分布,但是牺牲了ROWKEY的有序性
      • 缺点:利于Get操作,但不利于Scan操作,因为数据在原ROWKEY上的自然顺序已经被打乱
  2. 加盐策略

    • Salting(加盐)的原理是在原ROWKEY的前面添加固定长度的随机数,也就是给ROWKEY分配一个随机前缀使它和之间的ROWKEY的开头不同
    • 随机数能保障数据在所有Regions]的负载均衡
      • 缺点:因为添加的是随机数,基于原ROWKEY查询时无法知道随机数是什么,那样在查询的时候就需要去各个可能的Regions中查找,加盐对比读取是无力的
  3. 哈希策略

    • 基于 ROWKEY的完整或部分数据进行 Hash,而后将Hashing后的值完整替换或部分替换原ROWKEY的前缀部分
    • 这里说的hash包含MD5、shal、sha256或sha512等算法
      • 缺点:Hashing 也不利于 Scan,因为打乱了原RowKey的自然顺序

常用策略

  1. 预分区
    • 在创建表的时候,配置一些策略,让一个table有多个region,分布在不同的HRegionServer中
    • HBase会自动进行split,如果一个region过大,HBase会自动split成两个,就是根据rowkey来横向切分
  2. rowkey设计
    • 反转:举例:手机号码、时间戳,可以将手机号码反转
    • 加盐:在rowkey前面加随机数,加了随机数之后,就会导致数据查询不出来,因为HBase默认是没有二级索引的
    • hash:根据rowkey中的某个部分取hash,因为hash每次计算都一样的值。所以,我们可以用hash操作获取数据
    • 这几种策略,因为要将数据均匀分布在集群中的每个RegionServer,所以其核心就是把rowkey打散后放入到集群节点中,所以数据不再是有序的存储,会导致scan的效率下降

预分区例子

预分区

  • 在HBase中,可以通过指start key、end key来进行分区,还可以直接指定Region的数量,指定分区的策略。
  1. 指定 start key、end key来分区,
  • 通过指定分区数量创建表(使用 SPLITS_NUM 参数):

    create 'namespace:t1', 'f1', SPLITS_NUM => 5
    

    这个命令会将表 t1 分成 5 个预定义的Region。HBase会自动选择一种均匀分布的策略进行划分。

  • 通过 SPLITS 参数手动指定分区点:

    create 'namespace:t1', 'f1', SPLITS => ['10', '20', '30', '40', '50']
    

    这个命令会在创建表时使用指定的Row Key作为分区点,创建时生成6个Region(包括边界)。

  • 通过 SPLITS_FILE 指定分区点文件:

    如果分区点非常多,可以将分区点写入文件,然后通过 SPLITS_FILE 参数导入:

    create 'namespace:t1', 'f1', SPLITS_FILE => 'hdfs://path/to/splits_file', OWNER => 'Johndoe'
    
  1. 指定分区数量、分区策略

    create 't1', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
    
  • 分区策略
    • HexStringSplit:ROWKEY是十六进制的字符串作为前缀的
    • DecimalStringSplit:ROWKEY是10进制数字字符串作为前缀的
    • UniformSplit:ROWKEY前缀完全随机

Region的数量可以按照数据量来预估。例子,因为受限于硬件,所以我们设计为6个Region。因为ROWKEY我们是使用多个字段拼接,而且前缀不是完全随机的,所以需要使用HexStringSplit。

ROWKEY设计

  • 确保数据均匀分布到每个Region中 , 需要MD5Hash作为前缀
  • ROWKEY = MD5Hash_账号id_收件人id_时间戳

业务分区脚本

create 'MOMO_CHAT:MSG', {NAME => 'C1', COMPRESSION => 'GZ'}, {NUMREGIONS => 6, SPLITALGO => 'HexStringSplit'}

观察Hadoop HDFS中的内容 和 Hbase Web UI 中显示的内容

Region其实对应着HDFS中的文件

模拟数据

测试数据集下载

测试数据集下载

需要添加的maven依赖

        <!-- Xml操作相关 -->
        <dependency>
            <groupId>com.github.cloudecho</groupId>
            <artifactId>xmlbean</artifactId>
            <version>1.5.5</version>
        </dependency>
        <!-- 操作Office库 -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.0.1</version>
        </dependency>
        <!-- 操作Office库 -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.0.1</version>
        </dependency>
        <!-- 操作Office库 -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>4.0.1</version>
        </dependency>
        <!-- 操作JSON -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
        <!-- phoenix core -->
        <dependency>
            <groupId>org.apache.phoenix</groupId>
            <artifactId>phoenix-core</artifactId>
            <version>5.0.0-HBase-2.0</version>
        </dependency>
        <!-- phoenix 客户端 -->
        <dependency>
            <groupId>org.apache.phoenix</groupId>
            <artifactId>phoenix-queryserver-client</artifactId>
            <version>5.0.0-HBase-2.0</version>
        </dependency>

ExcelReader工具类

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.util.*;
import java.util.logging.Logger;

/**
 * @Title: ExcelReader
 * @Author David
 * @Package tool
 * @Date 2024/10/22 下午3:58
 * @description:
 */
public class ExcelReader {
    private static Logger log = Logger.getLogger("client");

    public static void main(String[] args) {
        String xlxsPath = "/home/David/Project/momo_chat_app/data/测试数据集.xlsx";
        Map<String, List<String>> mapData = readXlsx(xlxsPath, "陌陌数据");

        for(int i = 0; i < 10000; ++i) {
            System.out.println(randomColumn(mapData, "sender_nickyname"));
        }
    }

    /**
     * 随机获取某一列的数据
     * @param columnName 列名
     * @return 随机数据
     */
    public static String randomColumn(Map<String, List<String>> resultMap, String columnName) {
        List<String> valList = resultMap.get(columnName);

        if(valList == null) throw new RuntimeException("未读取到列名为" + columnName + "的任何数据!");
        Random random = new Random();

        int randomIndex = random.nextInt(valList.size());

        return valList.get(randomIndex);
    }

    /**
     * 将Excel文件读取为Map结构: <column_name, list>
     * 其中column_name为第4行的名字
     * @param path Excel文件路径(要求Excel为2007)
     * @param sheetName 工作簿名称
     * @return Map结构
     */
    public static Map<String, List<String>> readXlsx(String path, String sheetName)
    {
        // 列的数量
        int columnNum = 0;
        HashMap<String, List<String>> resultMap = new HashMap<String, List<String>>();
        ArrayList<String> columnList = new ArrayList<String>();

        try
        {
            OPCPackage pkg= OPCPackage.open(path);
            XSSFWorkbook excel=new XSSFWorkbook(pkg);
            //获取sheet
            XSSFSheet sheet=excel.getSheet(sheetName);

            // 加载列名
            XSSFRow columnRow = sheet.getRow(3);
            if(columnRow == null) {
                throw new RuntimeException("数据文件读取错误!请确保第4行为英文列名!");
            }
            else {
                Iterator<Cell> colIter = columnRow.iterator();
                // 迭代所有列
                while(colIter.hasNext()) {
                    Cell cell = colIter.next();
                    String colName = cell.getStringCellValue();
                    columnList.add(colName);
                    columnNum++;
                }
            }

            System.out.println("读取到:" + columnNum + "列");
            System.out.println(Arrays.toString(columnList.toArray()));

            // 初始化resultMap
            for(String colName : columnList) {
                resultMap.put(colName, new ArrayList<String>());
            }

            // 迭代sheet
            Iterator<Row> iter = sheet.iterator();
            int i = 0;
            int rownum = 1;

            while(iter.hasNext()) {
                Row row = iter.next();
                Iterator<Cell> cellIter = row.cellIterator();

                // 跳过前4行
                if(rownum <= 4) {
                    ++rownum;
                    continue;
                }

                while(cellIter.hasNext()) {
                    XSSFCell cell=(XSSFCell) cellIter.next();
                    //根据单元的的类型,读取相应的结果
                    if(cell.getCellType() == CellType.NUMERIC) {
                        resultMap.get(columnList.get(i % columnList.size())).add(Double.toString(cell.getNumericCellValue()));
                    }
                    else {
                        resultMap.get(columnList.get(i % columnList.size())).add(cell.getStringCellValue());
                    }

                    ++i;
                    ++rownum;
                }
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }

        return resultMap;
    }
}

Msg对象类

package entity;

/**
 * @Title: Msg
 * @Author David
 * @Package entity
 * @Date 2024/10/23 下午1:14
 * @description:
 */
public class Msg {
    private String msg_time;                // 消息时间
    private String sender_nickyname;        // 发件人昵称
    private String sender_account;          // 发件人账号
    private String sender_sex;              // 发件人性别
    private String sender_ip;               // 发件人IP
    private String sender_os;               // 发件人系统
    private String sender_phone_type;       // 发件人手机型号
    private String sender_network;          // 发件人网络制式
    private String sender_gps;              // 发件人GPS
    private String receiver_nickyname;      // 收件人昵称
    private String receiver_ip;             // 收件人IP
    private String receiver_account;        // 收件人账号
    private String receiver_os;             // 收件人系统
    private String receiver_phone_type;     // 收件人手机型号
    private String receiver_network;        // 收件人网络制式
    private String receiver_gps;            // 收件人GPS
    private String receiver_sex;            // 收件人性别
    private String msg_type;                // 消息类型
    private String distance;                // 双方距离
    private String message;                 // 消息

    // 自行添加getter setter toString
}

MoMoMsgGen数据导入类

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.io.compress.Compression;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.MD5Hash;
import org.apache.hadoop.hbase.util.RegionSplitter.HexStringSplit;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @Title: MoMoMsgGen
 * @Author David
 * @Package tool
 * @Date 2024/10/23 下午1:16
 * @description:
 */
public class MoMoMsgGen {
    public static void main(String[] args) throws ParseException, IOException {
        // 读取Excel文件中的数据
        Map<String, List<String>> resultMap =
                ExcelReader.readXlsx("/home/David/Project/momo_chat_app/data/测试数据集.xlsx", "陌陌数据");


        // 生成数据到HBase中
        // 1. 获取Hbase连接
        Configuration config = HBaseConfiguration.create();
        Connection connection = ConnectionFactory.createConnection(config);


        // 2. 获取HBase表MOMO_CHAT:MSG
        Table table = connection.getTable(TableName.valueOf("MOMO_CHAT:MSG"));

        Admin admin = connection.getAdmin();

        if(admin.tableExists(TableName.valueOf("MOMO_CHAT:MSG"))) {
            System.out.println("表已经存在");
        }else{
            System.out.println("表不存在");
            // Define table name
            TableName tableName = TableName.valueOf("MOMO_CHAT:MSG");

            // Define column family with GZ compression
            ColumnFamilyDescriptor columnFamilyDescriptor = ColumnFamilyDescriptorBuilder
                    .newBuilder("C1".getBytes())
                    .setCompressionType(Compression.Algorithm.GZ)
                    .build();

            // Define the table descriptor with the column family
            TableDescriptor tableDescriptor = TableDescriptorBuilder.newBuilder(tableName)
                    .setColumnFamily(columnFamilyDescriptor)
                    .build();

            // Use HexStringSplit to create 6 regions
            HexStringSplit splitter = new HexStringSplit();
            byte[][] splitKeys = splitter.split(6);

            // Create table with specified split keys
            admin.createTable(tableDescriptor, splitKeys);
        }


        int i = 0;
        int MAX = 100000;

        while (i < MAX) {
            Msg msg = getOneMessage(resultMap);
            // 3. 初始化操作Hbase所需的变量(列蔟、列名)
            byte[] rowkey = getRowkey(msg);
            String cf = "C1";
            String colMsg_time = "msg_time";
            String colSender_nickyname = "sender_nickyname";
            String colSender_account = "sender_account";
            String colSender_sex = "sender_sex";
            String colSender_ip = "sender_ip";
            String colSender_os = "sender_os";
            String colSender_phone_type = "sender_phone_type";
            String colSender_network = "sender_network";
            String colSender_gps = "sender_gps";
            String colReceiver_nickyname = "receiver_nickyname";
            String colReceiver_ip = "receiver_ip";
            String colReceiver_account = "receiver_account";
            String colReceiver_os = "receiver_os";
            String colReceiver_phone_type = "receiver_phone_type";
            String colReceiver_network = "receiver_network";
            String colReceiver_gps = "receiver_gps";
            String colReceiver_sex = "receiver_sex";
            String colMsg_type = "msg_type";
            String colDistance = "distance";
            String colMessage = "message";

            // 4. 构建put请求
            Put put = new Put(rowkey);

            // 5. 挨个添加陌陌消息的所有列
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colMsg_time), Bytes.toBytes(msg.getMsg_time()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colSender_nickyname), Bytes.toBytes(msg.getSender_nickyname()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colSender_account), Bytes.toBytes(msg.getSender_account()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colSender_sex), Bytes.toBytes(msg.getSender_sex()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colSender_ip), Bytes.toBytes(msg.getSender_ip()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colSender_os), Bytes.toBytes(msg.getSender_os()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colSender_phone_type), Bytes.toBytes(msg.getSender_phone_type()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colSender_network), Bytes.toBytes(msg.getSender_network()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colSender_gps), Bytes.toBytes(msg.getSender_gps()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colReceiver_nickyname), Bytes.toBytes(msg.getReceiver_nickyname()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colReceiver_ip), Bytes.toBytes(msg.getReceiver_ip()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colReceiver_account), Bytes.toBytes(msg.getReceiver_account()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colReceiver_os), Bytes.toBytes(msg.getReceiver_os()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colReceiver_phone_type), Bytes.toBytes(msg.getReceiver_phone_type()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colReceiver_network), Bytes.toBytes(msg.getReceiver_network()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colReceiver_gps), Bytes.toBytes(msg.getReceiver_gps()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colReceiver_sex), Bytes.toBytes(msg.getReceiver_sex()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colMsg_type), Bytes.toBytes(msg.getMsg_type()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colDistance), Bytes.toBytes(msg.getDistance()));
            put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(colMessage), Bytes.toBytes(msg.getMessage()));

            // 6. 发起put请求
            table.put(put);

            // 显示进度
            ++i;
            System.out.println(i + " / " + MAX);
        }
        table.close();
        connection.close();
    }

    /**
     * 基于从Excel表格中读取的数据随机生成一个Msg对象
     * @param resultMap Excel读取的数据(Map结构)
     * @return 一个Msg对象
     */
    public static Msg getOneMessage(Map<String, List<String>> resultMap) {
        // 1.	构建Msg实体类对象
        Msg msg = new Msg();

        // 将当前系统的时间设置为消息的时间,以年月日 时分秒的形式存储
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // 获取系统时间
        Date now = new Date();
        msg.setMsg_time(simpleDateFormat.format(now));

        // 2.	调用ExcelReader中的randomColumn随机生成一个列的数据
        // 初始化sender_nickyname字段,调用randomColumn随机取nick_name设置数据
        msg.setSender_nickyname(ExcelReader.randomColumn(resultMap, "sender_nickyname"));
        msg.setSender_account(ExcelReader.randomColumn(resultMap, "sender_account"));
        msg.setSender_sex(ExcelReader.randomColumn(resultMap, "sender_sex"));
        msg.setSender_ip(ExcelReader.randomColumn(resultMap, "sender_ip"));
        msg.setSender_os(ExcelReader.randomColumn(resultMap, "sender_os"));
        msg.setSender_phone_type(ExcelReader.randomColumn(resultMap, "sender_phone_type"));
        msg.setSender_network(ExcelReader.randomColumn(resultMap, "sender_network"));
        msg.setSender_gps(ExcelReader.randomColumn(resultMap, "sender_gps"));
        msg.setReceiver_nickyname(ExcelReader.randomColumn(resultMap, "receiver_nickyname"));
        msg.setReceiver_ip(ExcelReader.randomColumn(resultMap, "receiver_ip"));
        msg.setReceiver_account(ExcelReader.randomColumn(resultMap, "receiver_account"));
        msg.setReceiver_os(ExcelReader.randomColumn(resultMap, "receiver_os"));
        msg.setReceiver_phone_type(ExcelReader.randomColumn(resultMap, "receiver_phone_type"));
        msg.setReceiver_network(ExcelReader.randomColumn(resultMap, "receiver_network"));
        msg.setReceiver_gps(ExcelReader.randomColumn(resultMap, "receiver_gps"));
        msg.setReceiver_sex(ExcelReader.randomColumn(resultMap, "receiver_sex"));
        msg.setMsg_type(ExcelReader.randomColumn(resultMap, "msg_type"));
        msg.setDistance(ExcelReader.randomColumn(resultMap, "distance"));
        msg.setMessage(ExcelReader.randomColumn(resultMap, "message"));

        // 3.	注意时间使用系统当前时间

        return msg;
    }

    // 根据Msg实体对象生成rowkey
    public static byte[] getRowkey(Msg msg) throws ParseException {
        //
        // ROWKEY = MD5Hash_发件人账号_收件人账号_消息时间戳
        //
        // 使用StringBuilder将发件人账号、收件人账号、消息时间戳使用下划线(_)拼接起来
        StringBuilder builder = new StringBuilder();
        builder.append(msg.getSender_account());
        builder.append("_");
        builder.append(msg.getReceiver_account());
        builder.append("_");
        // 获取消息的时间戳
        String msgDateTime = msg.getMsg_time();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date msgDate = simpleDateFormat.parse(msgDateTime);
        long timestamp = msgDate.getTime();
        builder.append(timestamp);

        // 使用Bytes.toBytes将拼接出来的字符串转换为byte[]数组
        // 使用MD5Hash.getMD5AsHex生成MD5值,并取其前8位
        String md5AsHex = MD5Hash.getMD5AsHex(builder.toString().getBytes());
        String md5Hex8bit = md5AsHex.substring(0, 8);

        // 再将MD5值和之前拼接好地发件人账号、收件人账号、消息时间戳,再使用下划线拼接,转换为Bytes数组
        String rowkeyString = md5Hex8bit + "_" + builder.toString();
        // System.out.println(rowkeyString);

        return Bytes.toBytes(rowkeyString);
    }
}

构建RowKey分析

  1. RowKey = MD5Hash_发件人id_收件人id_消息时间戳
  2. 其中MD5Hash的计算方式为:发送人账号+""+收件人账号+""+消息时间戳
  3. 使用MD5Hash.getMD5AsHex生成MD5值
  4. 取MD5值前8位,避免过长
  5. 最后把发件人账号、收件人账号、消息时间戳和MD5拼接起来
  • 实现代码
    // 根据Msg实体对象生成rowkey
    public static byte[] getRowkey(Msg msg) throws ParseException {
        //
        // ROWKEY = MD5Hash_发件人账号_收件人账号_消息时间戳
        //
        // 使用StringBuilder将发件人账号、收件人账号、消息时间戳使用下划线(_)拼接起来
        StringBuilder builder = new StringBuilder();
        builder.append(msg.getSender_account());
        builder.append("_");
        builder.append(msg.getReceiver_account());
        builder.append("_");
        // 获取消息的时间戳
        String msgDateTime = msg.getMsg_time();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date msgDate = simpleDateFormat.parse(msgDateTime);
        long timestamp = msgDate.getTime();
        builder.append(timestamp);

        // 使用Bytes.toBytes将拼接出来的字符串转换为byte[]数组
        // 使用MD5Hash.getMD5AsHex生成MD5值,并取其前8位
        String md5AsHex = MD5Hash.getMD5AsHex(builder.toString().getBytes());
        String md5Hex8bit = md5AsHex.substring(0, 8);

        // 再将MD5值和之前拼接好地发件人账号、收件人账号、消息时间戳,再使用下划线拼接,转换为Bytes数组
        String rowkeyString = md5Hex8bit + "_" + builder.toString();
        // System.out.println(rowkeyString);

        return Bytes.toBytes(rowkeyString);
    }

性能问题

  • Hbase默认只支持对行键的索引,那么如果要针对其它的列来进行查询,就只能全表扫描。
  • 上述的查询是使用scan+filter组合来进行查询的,但查询地效率不高,因为要进行顺序全表扫描而没有其他索引。如果数据量较大,只能在客户端(client)来进行处理,如果要传输到Client大量的数据,然后交由客户端处理
    • 网络传输压力很大
    • 客户端的压力很大
  • 如果表存储的数量很大时,效率会非常低下,此时需要使用二级索引
  • 也就是除了ROWKEY索引外,还需要人为添加其他的方便查询的索引

如果每次需要我们开发二级索引来查询数据,这样使用起来很麻烦。再者,查询数据都是HBaseJava API,使用起来不是很方便。为了让其他开发人员更容易使用该接口。如果有一种SQL引擎,通过 SQL语句来查询数据会更加方便。