图文详解:内存总是不够,我靠HBase说服了Leader为新项目保驾护航

news2024/11/28 8:31:17

最近在工作中用到了 Hbase 这个数据库,也顺便做了关于 Hbase 的知识记录来分享给大家。其实 Hbase的内容体系真的很多很多,这里介绍的是小羽认为在工作中会用到的一些技术点,希望可以帮助到大家。

可以这么说互联网都是建立在形形色色的数据库之上的,现在主流的数据库有这么几种:以 MySQL 为代表的关系型数据库以及其分布式解决方案,以 Redis 为代表的缓存数据库,以 ES 为代表的检索数据库,再就是分布式持久化 KV 数据库。而在开源领域,尤其是国内,HBase 几乎是分布式持久化KV数据库的首选方案。HBase 应用的业务场景非常之多,比如用户画像、实时(离线)推荐、实时风控、社交Feed流、商品历史订单、社交聊天记录、监控系统以及用户行为日志等等。

前言

我们每一个人无论使用什么科技产品,都会产生大量的数据,而这些数据的存储和查询对于小型数据库来说其实是很难满足我们的需求的,因此出现了 HBase 分布式大数据。HBase 是一个构建在 Hadoop 文件系统之上的面向列的数据库管理系统。HBase 是一种类似于 Google’s Big Table 的数据模型,它是 Hadoop 生态系统的一部分,它将数据存储在 HDFS 上,客户端可以通过 HBase 实现对 HDFS 上数据的随机访问。它主要有以下特性:

不支持复杂的事务,只支持行级事务,即单行数据的读写都是原子性的;

由于是采用 HDFS 作为底层存储,所以和 HDFS 一样,支持结构化、半结构化和非结构化的存储;

支持通过增加机器进行横向扩展;

支持数据分片

支持 RegionServers 之间的自动故障转移

易于使用的 Java 客户端 API

支持 BlockCache 和布隆过滤器

过滤器支持谓词下推

HBase 原理

概念

HBase 是分布式、面向列的开源数据库(其实准确的说是面向列族)。HDFS 为 Hbase 提供可靠的底层数据存储服务MapReduce 为 Hbase 提供高性能的计算能力Zookeeper 为 Hbase 提供稳定服务和 Failover 机制,因此我们说 Hbase 是一个通过大量廉价的机器解决海量数据的高速存储和读取的分布式数据库解决方案

列式存储

我们先来看一下之前的关系型数据库的按行来存储的。如下图:

可以看到只有第一行 ID:1 小羽的这一行的数据都填了,小娜和小智的数据都没有填完。在我们的行结构中,都是固定的,每一行都一样,就算不填,也要空着,不能没有。

来看一下使用了非关系型数据库的按列存储的效果图:

可以看到之前小羽的一列数据对应到了小羽现在的一行数据,原来小羽的七列数据变成了现在的七行。之前的七行数据在一行,共用过一个主键 ID:1 。在列式存储里,变成了七行,每一行都有一个主键与其对应,也就是为什么小羽的主键 ID:1 重复了七次。这样排列最大的好处就是,我们对于不需要的数据就不需要添加,会大大节省我们的空间资源。因为查询中的选择规则是通过列来定义的,整个数据库是自动索引化的。

NoSQL和关系型数据库对比

对比如下图

RDBMS 与 Hbase 对比

Hbase 是根据列族来存储数据的。列族下面可以有非常多的列,列族在创建表的时候就必须指定。为了加深对 Hbase 列族的理解,下面是简单的关系型数据库的表和 Hbase 数据库的表:

主要区别

HBase 架构

Hbase 是由 Client、Zookeeper、Master、HRegionServer、HDFS 等几个核心体系组成。

Client

Client 使用 HBase 的 RPC 机制与 HMaster、HRegionServer 进行通信。Client 与 HMaster 进行管理类通信,与 HRegion Server 进行数据操作类通信

Zookeeper

Hbase 通过 Zookeeper 来做 master 的高可用、RegionServer 的监控、元数据的入口以及集群配置的维护等工作。具体工作如下:

1. 通过 Zoopkeeper 来保证集群中只有 1 个 master 在运行,如果 master 异常,会通过竞争机制产生新的 master 提供服务

2. 通过 Zoopkeeper 来监控 RegionServer 的状态,当 RegionSevrer 有异常的时候,通过回调的形式通知 Master RegionServer 上下限的信息

3. 通过 Zoopkeeper 存储元数据的统一入口地址

客户端在使用 hbase 的时候,需要添加 zookeeper 的 ip 地址和节点路径,建立起与zookeeper的连接,建立连接的方式如下面的代码所示:

Configuration configuration = HBaseConfiguration.create();
configuration.set("hbase.zookeeper.quorum", "XXXX.XXX.XXX");
configuration.set("hbase.zookeeper.property.clientPort", "2181");
configuration.set("zookeeper.znode.parent", "XXXXX");
Connection connection = ConnectionFactory.createConnection(configuration);

Hmaster

master 节点的主要职责如下:

1. 为 RegionServer 分配 Region

2. 维护整个集群的负载均衡

3. 维护集群的元数据信息,发现失效的 Region,并将失效的 Region 分配到正常RegionServer 上当 RegionSever 失效的时候,协调对应 Hlog 的拆分

HRegionServer

HRegionServer 内部管理了一系列 HRegion 对象,每个 HRegion 对应 Table中的一个 ColumnFamily 的存储,即一个 Store 管理一个 Region 上的一个列族(CF)。每个 Store 包含一个 MemStore 和 0 到多个 StoreFile。Store 是 HBase 的存储核心,由 MemStore 和 StoreFile 组成。

HLog

数据在写入时,首先写入预写日志(Write Ahead Log),每个 HRegionServer 服务的所有 Region 的写操作日志都存储在同一个日志文件中。数据并非直接写入 HDFS,而是等缓存到一定数量再批量写入,写入完成后在日志中做标记

MemStore

MemStore 是一个有序的内存缓存区,用户写入的数据首先放入 MemStore,当 MemStore 满了以后 Flush 成一个 StoreFile(存储时对应为 File),当 StoreFile 数量增到一定阀值,触发 Compact 合并,将多个 StoreFile 合并成一个 StoreFile。StoreFiles 合并后逐步形成越来越大的 StoreFile,当 Region 内所有 StoreFiles(Hfile) 的总大小超过阀值(hbase.hregion.max.filesize)即触发分裂 Split,把当前的 Region Split 分成 2 个 Region,父 Region 下线,新 Spilt 出的 2 个孩子 Region 被 HMaster 分配到合适的 HRegionServer 上,使得原先 1 个 Region 的压力得以分流到 2 个 Region 上。

Region 寻址方式

通过 zookeeper.META,主要有以下几步:

1. Client 请求 ZK 获取.META.所在的 RegionServer 的地址

2. Client 请求.META.所在的 RegionServer 获取访问数据所在的 RegionServer 地址,client 会将.META.的相关信息 cache 下来,以便下一次快速访问。

3. Client 请求数据所在的 RegionServer,获取所需要的数据

HDFS

HDFS 为 Hbase 提供最终的底层数据存储服务,同时为 Hbase 提供高可用(Hlog 存储在HDFS)的支持。

HBase 组件

Column Family 列族

Column Family 又叫列族,Hbase 通过列族划分数据的存储,列族下面可以包含任意多的列,实现灵活的数据存取。Hbase 表的创建的时候就必须指定列族。就像关系型数据库创建的时候必须指定具体的列是一样的。Hbase 的列族不是越多越好,官方推荐的是列族最好小于或者等于 3。我们使用的场景一般是 1 个列族。

Rowkey

Rowkey 的概念和 mysql 中的主键是完全一样的,Hbase 使用 Rowkey 来唯一的区分某一行的数据。Hbase 只支持 3 种查询方式:基于 Rowkey 的单行查询,基于 Rowkey 的范围扫描全表扫描

Region 分区

Region:Region 的概念和关系型数据库的分区或者分片差不多。Hbase 会将一个大表的数据基于 Rowkey 的不同范围分配到不同的 Region 中,每个 Region 负责一定范围的数据访问和存储。这样即使是一张巨大的表,由于被切割到不同的 region,访问起来的时延也很低

TimeStamp 多版本

TimeStamp 是实现 Hbase 多版本的关键。在 Hbase 中使用不同的 timestame 来标识相同 rowkey 行对应的不同版本的数据。在写入数据的时候,如果用户没有指定对应的 timestamp,Hbase 会自动添加一个 timestamp,timestamp 和服务器时间保持一致。在Hbase 中,相同 rowkey 的数据按照 timestamp 倒序排列。默认查询的是最新的版本,用户可通过指定 timestamp 的值来读取旧版本的数据。

Hbase 写逻辑

Hbase 写入流程

主要有三个步骤:

1. Client 获取数据写入的 Region 所在的 RegionServer

2. 请求写 Hlog, Hlog 存储在 HDFS,当 RegionServer 出现异常,需要使用 Hlog 来恢复数据

3. 请求写 MemStore,只有当写 Hlog 和写 MemStore 都成功了才算请求写入完成。MemStore 后续会逐渐刷到 HDFS 中。

MemStore 刷盘

为了提高 Hbase 的写入性能,当写请求写入 MemStore 后,不会立即刷盘。而是会等到一定的时候进行刷盘的操作。具体是哪些场景会触发刷盘的操作呢?总结成如下的几个场景:

1. 这个全局的参数是控制内存整体的使用情况,当所有 memstore 占整个 heap 的最大比例的时候,会触发刷盘的操作。这个参数是hbase.regionserver.global.memstore.upperLimit,默认为整个 heap 内存的 40%。但这并不意味着全局内存触发的刷盘操作会将所有的 MemStore 都进行输盘,而是通过另外一个参数hbase.regionserver.global.memstore.lowerLimit 来控制,默认是整个 heap 内存的 35%。当 flush 到所有 memstore 占整个 heap 内存的比率为35%的时候,就停止刷盘。这么做主要是为了减少刷盘对业务带来的影响,实现平滑系统负载的目的。

2. 当 MemStore 的大小达到 hbase.hregion.memstore.flush.size 大小的时候会触发刷盘,默认 128M 大小

3. 前面说到 Hlog 为了保证 Hbase 数据的一致性,那么如果 Hlog 太多的话,会导致故障恢复的时间太长,因此 Hbase 会对 Hlog 的最大个数做限制。当达到 Hlog 的最大个数的时候,会强制刷盘。这个参数是 hase.regionserver.max.logs,默认是 32 个。

4. 可以通过 hbase shell 或者 java api 手工触发 flush 的操作。

5. 在正常关闭 RegionServer 会触发刷盘的操作,全部数据刷盘后就不需要再使用 Hlog 恢复数据。

6. 当 RegionServer 出现故障的时候,其上面的 Region 会迁移到其他正常的 RegionServer 上,在恢复完 Region 的数据后,会触发刷盘,当刷盘完成后才会提供给业务访问。

HBase 中间层

Phoenix 是 HBase 的开源 SQL 中间层,它允许你使用标准 JDBC 的方式来操作 HBase 上的数据。在 Phoenix 之前,如果你要访问 HBase,只能调用它的 Java API,但相比于使用一行 SQL 就能实现数据查询,HBase 的 API 还是过于复杂。Phoenix 的理念是 we put sql SQL back in NOSQL,即你可以使用标准的 SQL 就能完成对 HBase 上数据的操作。同时这也意味着你可以通过集成Spring Data JPA 或 Mybatis 等常用的持久层框架来操作 HBase。

其次 Phoenix 的性能表现也非常优异,Phoenix 查询引擎会将 SQL 查询转换为一个或多个 HBase Scan,通过并行执行来生成标准的 JDBC 结果集。它通过直接使用 HBase API 以及协处理器和自定义过滤器,可以为小型数据查询提供毫秒级的性能,为千万行数据的查询提供秒级的性能。同时 Phoenix 还拥有二级索引等 HBase 不具备的特性,因为以上的优点,所以 Phoenix 成为了 HBase 最优秀的 SQL 中间层。

HBase 安装使用

下载 HBase 压缩包,首先解压

tar -zxvf hbase-0.98.6-hadoop2-bin.tar.gz

打开 hbase-env.sh 文件配置 JAVA_HOME:

export JAVA_HOME=/opt/modules/jdk1.7.0_79

配置 hbase-site.xml:

<configuration>
 <property>
    <name>hbase.rootdir</name>
    <value>hdfs://hadoop-senior.shinelon.com:8020/hbase</value>
  </property>
  <property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
  </property>
  <property>
    <name>hbase.zookeeper.quorum</name>
    <value>hadoop-senior.shinelon.com</value>
  </property>
</configuration>

将上面的主机名换为自己的主机名,就可以启动HBase了。Web 页面访问如下:

HBase 命令

下面是小羽整理的一些关于 Hbase 的经常会使用到的命令

HBase API 使用

API 如下

package com.initialize;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.regionserver.BloomType;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
/**
 *  
 *  1、构建连接
 *  2、从连接中取到一个表DDL操作工具admin
 *  3、admin.createTable(表描述对象);
 *  4、admin.disableTable(表名);
 * 5、admin.deleteTable(表名);
 * 6、admin.modifyTable(表名,表描述对象); 
 *
 */
public class HbaseClientDemo {

    Connection conn = null;

    @Before
    public void getConn() throws IOException {
        //构建一个连接对象
        Configuration conf = HBaseConfiguration.create();//会自动加载hbase-site.xml
        conf.set("hbase.zookeeper.quorum","n1:2181,n2:2181,n3:2181");

        conn = ConnectionFactory.createConnection(conf);
    }

    /**
     * DDL
     * 创建表
     */
    @Test
    public void testCreateTable() throws  Exception{

        //从连接中构造一个DDL操作器
        Admin admin = conn.getAdmin();

        //创建一个标定义描述对象
        HTableDescriptor hTableDescriptor = new HTableDescriptor(TableName.valueOf("user_info"));

        //创建列族定义描述对象
        HColumnDescriptor hColumnDescriptor_1 = new HColumnDescriptor("base_info");
        hColumnDescriptor_1.setMaxVersions(3);

        HColumnDescriptor hColumnDescriptor_2 = new HColumnDescriptor("extra_info");

        //将列族定义信息对象放入表定义对象中
        hTableDescriptor.addFamily(hColumnDescriptor_1);
        hTableDescriptor.addFamily(hColumnDescriptor_2);

        //用ddl操作器对象:admin来创建表
        admin.createTable(hTableDescriptor);

        //关闭连接
        admin.close();
        conn.close();

    }

    /**
     * 删除表
     */
    @Test
    public void testDropTable() throws  Exception{

        Admin admin = conn.getAdmin();

        //停用表
        admin.disableTable(TableName.valueOf("user_info"));
        //删除表
        admin.deleteTable(TableName.valueOf("user_info"));

        admin.close();
        conn.close();
    }

    /**
     * 修改表定义--添加一个列族
     */
    @Test
    public void testAlterTable() throws  Exception{

        Admin admin = conn.getAdmin();

        //取出旧的表定义信息
        HTableDescriptor tableDescriptor = admin.getTableDescriptor(TableName.valueOf("user_info"));

        //新构造一个列族定义
        HColumnDescriptor hColumnDescriptor = new HColumnDescriptor("other_info");
        hColumnDescriptor.setBloomFilterType(BloomType.ROWCOL);//设置该列族的布隆过滤器类型

        //将列族定义添加到表定义对象中
        tableDescriptor.addFamily(hColumnDescriptor);

        //将修改过的表定义交给admin去提交
        admin.modifyTable(TableName.valueOf("user_info"), tableDescriptor);

        admin.close();
        conn.close();
    }
}

示例如下

package com.initialize;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellScanner;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;

public class HbaseClientDML {

    Connection conn = null;

    @Before
    public void getConn() throws IOException {
        //构建一个连接对象
        Configuration conf = HBaseConfiguration.create();//会自动加载hbase-site.xml
        conf.set("hbase.zookeeper.quorum","n1:2181,n2:2181,n3:2181");

        conn = ConnectionFactory.createConnection(conf);
    }


    /**
     * 增,改:put来覆盖
     */
    @Test
    public void testPut() throws  Exception{

        //获取一个操作指定表的table对象,进行DML操作
        Table table = conn.getTable(TableName.valueOf("user_info"));

        //构造要插入的数据为一个Put类型(一个put对象只能对应一个rowkey)的对象
        Put put = new Put(Bytes.toBytes("001"));
        put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"),Bytes.toBytes("小羽"));
        put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("age"), Bytes.toBytes("18"));
        put.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"), Bytes.toBytes("成都"));

        Put put2 = new Put(Bytes.toBytes("002"));
        put2.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("小娜"));
        put2.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("age"), Bytes.toBytes("17"));
        put2.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"), Bytes.toBytes("成都"));

        ArrayList<Put> puts = new ArrayList<>();
        puts.add(put);
        puts.add(put2);

        //插进去
        table.put(puts);

        table.close();
        conn.close();
    }

    /***
     * 循环插入大量数据
     */
    @Test
    public void testManyPuts() throws Exception{

        Table table = conn.getTable(TableName.valueOf("user_info"));
        ArrayList<Put> puts = new ArrayList<>();

        for(int i=0;i<10000;i++){
            Put put = new Put(Bytes.toBytes(""+i));
            put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("usernaem"), Bytes.toBytes("小羽" +i));
            put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("age"), Bytes.toBytes((18+i) + ""));
            put.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"), Bytes.toBytes("成都"));

            puts.add(put);
        }

        table.put(puts);
    }

    /**
     * 删
     */
    @Test
    public void testDelete() throws Exception{
        Table table = conn.getTable(TableName.valueOf("user_info"));

        //构造一个对象封装要删除的数据信息
        Delete delete1 = new Delete(Bytes.toBytes("001"));

        Delete delete2 = new Delete(Bytes.toBytes("002"));
        delete2.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"));

        ArrayList<Delete> dels = new ArrayList<>();
        dels.add(delete1);
        dels.add(delete2);

        table.delete(dels);

        table.close();
        conn.close();
    }

    /**
     * 查
     */
    @Test
    public void  testGet() throws Exception{

        Table table = conn.getTable(TableName.valueOf("user_info"));

        Get get = new Get("002".getBytes());

        Result result = table.get(get);

        //从结果中取用户指定的某个key和value的值
        byte[] value = result.getValue("base_info".getBytes(), "age".getBytes());
        System.out.println(new String(value));

        System.out.println("======================");

        //遍历整行结果中的所有kv单元格
        CellScanner cellScanner = result.cellScanner();
        while(cellScanner.advance()){
            Cell cell = cellScanner.current();

            byte[] rowArray = cell.getRowArray();//本kv所属行键的字节数组
            byte[] familyArray = cell.getFamilyArray();//列族名的字节数组
            byte[] qualifierArray = cell.getQualifierArray();//列名的字节数组
            byte[] valueArray = cell.getValueArray();//value字节数组
            
            System.out.println("行键:" + new String(rowArray, cell.getRowOffset(), cell.getRowLength()));
            System.out.println("列族名:" + new String(familyArray, cell.getFamilyOffset(), cell.getFamilyLength()));
            System.out.println("列名:" + new String(qualifierArray, cell.getQualifierOffset(), cell.getQualifierLength()));
            System.out.println("value:" + new String(valueArray, cell.getValueOffset(), cell.getValueLength()));

        }
        table.close();
        conn.close();

    }


    /**
     * 按行键范围查询数据
     */
    @Test
    public void testScan() throws Exception{

        Table table = conn.getTable(TableName.valueOf("user_info"));

        //包含起始行键,不包含结束行键,但是如果真的是想要查询出末尾的那个行键,可以在尾行键上拼接一个不可见的字符(\000)
        Scan scan = new Scan("10".getBytes(), "10000\001".getBytes());

        ResultScanner scanner =table.getScanner(scan);

        Iterator<Result> iterator = scanner.iterator();

        while(iterator.hasNext()){

            Result result =iterator.next();
            //遍历整行结果中的所有kv单元格
            CellScanner cellScanner = result.cellScanner();
            while(cellScanner.advance()){
                Cell cell = cellScanner.current();

                byte[] rowArray = cell.getRowArray();//本kv所属行键的字节数组
                byte[] familyArray = cell.getFamilyArray();//列族名的字节数组
                byte[] qualifierArray = cell.getQualifierArray();//列明的字节数组
                byte[] valueArray = cell.getValueArray();//value字节数组

                System.out.println("行键:" + new String(rowArray, cell.getRowOffset(), cell.getRowLength()));
                System.out.println("列族名:" + new String(familyArray, cell.getFamilyOffset(), cell.getFamilyLength()));
                System.out.println("列名:" + new String(qualifierArray, cell.getQualifierOffset(), cell.getQualifierLength()));
                System.out.println("value:" + new String(valueArray, cell.getValueOffset(), cell.getValueLength()));
            }
            System.out.println("----------------------");
        }
    }

    @Test
    public void test(){
        String a = "000";
        String b = "000\0";

        System.out.println(a);
        System.out.println(b);

        byte[] bytes = a.getBytes();
        byte[] bytes2 = b.getBytes();
        
        System.out.println("");
    }
}

HBase 应用场景

对象存储系统

HBase MOB(Medium Object Storage),中等对象存储是 hbase-2.0.0 版本引入的新特性,用于解决 hbase 存储中等文件(0.1m~10m)性能差的问题。这个特性适合将图片、文档、PDF、小视频存储到 Hbase 中。

OLAP 的存储

Kylin 的底层用的是 HBase 的存储,看中的是它的高并发和海量存储能力。kylin 构建 cube 的过程会产生大量的预聚合中间数据,数据膨胀率高,对数据库的存储能力有很高要求。

Phoenix 是构建在 HBase 上的一个 SQL 引擎,通过 phoenix 可以直接调用 JDBC 接口操作 Hbase,虽然有 upsert 操作,但是更多的是用在 OLAP 场景,缺点是非常不灵活

时序型数据

openTsDB 应用,记录以及展示指标在各个时间点的数值,一般用于监控的场景,是 HBase 上层的一个应用。

用户画像系统

动态列,稀疏列的特性。用于描述用户特征的维度数是不定的且可能会动态增长的(比如爱好,性别,住址等),不是每个特征维度都会有数据。

消息/订单系统

强一致性,良好的读性能,hbase 可以保证强一致性

feed 流系统存储

feed 流系统具有读多写少、数据模型简单、高并发、波峰波谷式访问、持久化可靠性存储、消息排序这些特点,比如说 HBase 的 rowKey 按字典序排序正好适用于这个场景。

Hbase 优化

预先分区

默认情况下,在创建 HBase 表的时候会自动创建一个 Region 分区,当导入数据的时候,所有的 HBase 客户端都向这一个 Region 写数据,直到这个 Region 足够大了才进行切分。一种可以加快批量写入速度的方法是通过预先创建一些空的 Regions,这样当数据写入 HBase 时,会按照 Region 分区情况,在集群内做数据的负载均衡

Rowkey 优化

HBase 中 Rowkey 是按照字典序存储,因此,设计 Rowkey 时,要充分利用排序特点,将经常一起读取的数据存储到一块,将最近可能会被访问的数据放在一块。

此外,Rowkey 若是递增的生成,建议不要使用正序直接写入 Rowkey,而是采用reverse 的方式反转 Rowkey,使得 Rowkey 大致均衡分布,这样设计有个好处是能将 RegionServer 的负载均衡,否则容易产生所有新数据都在一个 RegionServer 上堆积的现象,这一点还可以结合 table 的预切分一起设计。

减少列族数量

不要在一张表里定义太多的 ColumnFamily。目前 Hbase 并不能很好的处理超过2~3 个 ColumnFamily 的表。因为某个 ColumnFamily 在 flush 的时候,它邻近的 ColumnFamily 也会因关联效应被触发 flush,最终导致系统产生更多的 I/O。

缓存策略

创建表的时候,可以通过 HColumnDescriptor.setInMemory(true) 将表放到 RegionServer 的缓存中,保证在读取的时候被 cache 命中。

设置存储生命期

创建表的时候,可以通过 HColumnDescriptor.setTimeToLive(int timeToLive) 设置表中数据的存储生命期,过期数据将自动被删除。

硬盘配置

每台 RegionServer 管理 10~1000 个 Regions,每个 Region 在 1~2G,则每台 Server 最少要 10G,最大要1000*2G=2TB,考虑 3 备份,则要 6TB。方案一是用 3 块 2TB 硬盘,二是用 12 块 500G 硬盘,带宽足够时,后者能提供更大的吞吐率,更细粒度的冗余备份,更快速的单盘故障恢复。

分配合适的内存给 RegionServer 服务

在不影响其他服务的情况下,越大越好。例如在 HBase 的 conf 目录下的 hbase-env.sh 的最后添加 export HBASE_REGIONSERVER_OPTS="-Xmx16000m$HBASE_REGIONSERVER_OPTS”,其中 16000m 为分配给 RegionServer 的内存大小

写数据的备份数

备份数与读性能成正比,与写性能成反比,且备份数影响高可用性。有两种配置方式,一种是将 hdfs-site.xml拷贝到 hbase 的 conf 目录下,然后在其中添加或修改配置项 dfs.replication 的值为要设置的备份数,这种修改对所有的 HBase 用户表都生效,另外一种方式,是改写 HBase 代码,让 HBase 支持针对列族设置备份数,在创建表时,设置列族备份数,默认为 3,此种备份数只对设置的列族生效

WAL(预写日志)

可设置开关,表示 HBase 在写数据前用不用先写日志,默认是打开,关掉会提高性能,但是如果系统出现故障(负责插入的 RegionServer 挂掉),数据可能会丢失。配置 WAL 在调用 JavaAPI 写入时,设置 Put 实例的 WAL,调用Put.setWriteToWAL(boolean)

批量写

HBase 的 Put 支持单条插入,也支持批量插入,一般来说批量写更快,节省来回的网络开销。在客户端调用 JavaAPI 时,先将批量的 Put 放入一个 Put 列表,然后调用 HTable 的 Put(Put 列表) 函数来批量写

最后

在理解 HBase 时,可以发现 HBase 的设计其实和 Elasticsearch 十分相似,如 HBase 的 Flush&Compact 机制等设计与 Elasticsearch 如出一辙,因此理解起来比较顺利。

从本质上来说,HBase 的定位是分布式存储系统,Elasticsearch 是分布式搜索引擎,两者并不等同,但两者是互补的。HBase 的搜索能力有限,只支持基于RowKey 的索引,其它二级索引等高级特性需要自己开发。因此,有一些案例是结合 HBase 和 Elasticsearch 实现存储 + 搜索的能力。通过 HBase 弥补 Elasticsearch 存储能力的不足,通过 Elasticsearch 弥补 HBase 搜索能力的不足。

其实,不只是 HBase 和 Elasticsearch。任何一种分布式框架或系统,它们都有一定的共性,不同之处在于各自的关注点不同。小羽的感受是,在学习分布式中间件时,应先弄清其核心关注点,再对比其它中间件,提取共性和特性,进一步加深理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/142860.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

剑指offer----C语言版----第十天

目录 1. 二进制中 1 的个数 1.1 题目描述 1.2 可能引起错误的解法 1.3 常规解法 1.4 思路优化 1. 二进制中 1 的个数 原题链接: 剑指 Offer 15. 二进制中1的个数 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/er-jin-zhi-zhong-1de-ge-shu-lcof…

电路方案分析(十五)符合 EMC 标准的汽车制动灯和尾灯设计方案

符合 EMC 标准的汽车制动灯和尾灯设计方案 tips&#xff1a;TI设计方案参考分析&#xff1a;TI Designs&#xff1a;TIDA-01374 1.系统描述 1.1关键参数 2.系统概述 2.1系统框图 2.2关键元器件 3.设计原理 3.1双重亮度设计 3.2 电荷泵设计 3.4 LED故障设计 3.3 MOSFET驱动电…

LeetCode Hot 100 笔记

文章目录链表21. 合并两个有序链表栈20. 有效的括号Java栈链表 链表的题目一般都不太难&#xff0c;画图&#xff0c;别怕麻烦 21. 合并两个有序链表 解法一&#xff1a;迭代 用一个指针cur跟踪当前节点&#xff0c;每次从list1和list2中选取小的节点&#xff0c;链接起来建…

什么是轻量化,轻量化模型is all your need hhh

其实学了几个小月&#xff0c;我们肯定知道&#xff0c;MLP有多deeper &#xff0c;卷积层有多少层呀 抑或是Transformer架构&#xff0c;大量的参数&#xff0c;只能用huge 来描述&#xff0c; 可实际上我们的设备&#xff0c;有时候并没有服务器那么厉害&#xff0c;所以人…

阿里云认证为什么那么多人考?考试内容难不难?

我国人口密集&#xff0c;每年有大量的毕业生涌进社会&#xff0c;除此之外还有很多进入社会很久的打工人&#xff0c;想要跳槽&#xff0c;到更加挣钱的岗位&#xff0c;待遇更好的公司去。为了能够早日买房、买车&#xff0c;很多人会选择社会热门行业去学习&#xff0c;甚至…

sqli-labs 第七关 多命通关攻略

sqli-labs 第七关 多命通关攻略描述字符串与数值之间的转换判断注入类型返回结果正常输入不正常输入错误输入总结判断注入类型判断是否为字符型注入判断是否为单引号字符型注入判断是否为双引号字符型注入判断是否为数值型注入总结判断注入类型&#xff08;修正版&#xff09;字…

二十六、Docker (2)

&#x1f33b;&#x1f33b; 目录一、Docker的常用命令 (阶段A)1.1 帮助命令1.2 镜像命令1.3 容器命令1.3.1 新建容器并启动1.3.2 列出所有运行的容器1.3.3 退出容器1.3.4 删除容器1.3.5 启动和停止容器的操作1.4 常用的其它命令1.4.1 后台启动容器1.4.2 查看日志1.4.3 查看容器…

如何搭建私域流量?

如今已经进入存量用户时代&#xff0c;越来越多的企业也明白了存量用户的重要性&#xff0c;因此企业都非常重视私域流量的搭建&#xff0c;以挖掘客户的价值。 前言 如今已经进入存量用户时代&#xff0c;越来越多的企业也明白了存量用户的重要性&#xff0c;因此企业都非常重…

安卓搭建好的模拟机,为调试准备

​ 这一节直接分享制作好的虚拟机&#xff0c;镜像系统&#xff0c;以及安卓源码&#xff0c;直接节省你的时间去配置&#xff0c;编译。 下来我来分享下搭建步骤&#xff1a; 1 虚拟机下载vm 12 pro &#xff0c;这个网上百度就可以&#xff0c;原则13,14也都是可以的。 2 下…

移动魔百盒CM311-3-YST-晨星MSO9385-语音首页正常-TTL刷机包

移动魔百盒CM311-3-YST-晨星MSO9385-语音首页正常-TTL刷机包 固件特点&#xff1a; 1、三网通用&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、无开机广告&#xff0c;无系统更新&#xff0c;不在被强制升级&#xff1b; 4、大量精简内置的没用…

熬夜搞了 17000 字,终于把你这个 ES 玩明白了

平常经常用ES(ElasticSearch), 觉得这真是个好玩意儿&#xff0c;所以来分享一篇文章&#xff0c;希望通过这篇文章能让读者大致了解ES是做什么的以及它的使用和基本原理。 可能有的读者航海不知道ES是个啥玩儿&#xff0c;别着急&#xff0c;看完本文后&#xff0c;相信你会了…

import...from... 和 require 如何找到模块位置?

import Vue from "vue"; 为什么不用写相对地址和绝对地址就能够导出 Vue 呢&#xff1f;似乎也没有配置路径&#xff1f;也没有配置映射&#xff0c;那么究竟 from "vue"; 对应的究竟是那个路径呢&#xff1f; 先提出两个可能的方案 1.VS Code/WebStorm …

【每天学习一点新知识】nmap端口扫描

nmap所识别的6个端口状态open(开放的)应用程序正在该端口接收TCP 连接或者UDP报文。发现这一点常常是端口扫描 的主要目标。安全意识强的人们知道每个开放的端口 都是攻击的入口。攻击者或者入侵测试者想要发现开放的端口。 而管理员则试图关闭它们或者用防火墙保护它们以免妨碍…

Java封装公共Result结果返回类

前言 在使用Java开发接口请求中&#xff0c;我们需要对请求进行进行统一返回值&#xff0c;这时候我们自己封装一个统一的Result返回类&#xff0c;下面就介绍下我用的这种的这个类 当然&#xff0c;也可以使用第三方库封装的Result结果返回类&#xff0c;根据个人喜好选择即可…

学习笔记之Vue脚手架(三)

&#xff08;三&#xff09;使用Vue脚手架 使用Vue脚手架&#xff08;三&#xff09;使用Vue脚手架一、创建Vue脚手架1.1 说明1.2 具体步骤二、分析脚手架结构2.1 配置文件2.2 src文件夹2.3 public文件夹一、创建Vue脚手架 1.1 说明 1.Vue脚手架是Vue官方提供的标准开发工具&…

电脑技巧:分享常用的电脑快捷键

❤️作者主页&#xff1a;IT技术分享社区 ❤️作者简介&#xff1a;大家好,我是IT技术分享社区的博主&#xff0c;从事C#、Java开发九年&#xff0c;对数据库、C#、Java、前端、运维、电脑技巧等经验丰富。 ❤️个人荣誉&#xff1a; 数据库领域优质创作者&#x1f3c6;&#x…

Nutanix 替代专题 | SmartX 与 Nutanix 超融合市场、技术与性能对比

2022 年 8 月 19 日&#xff0c;Nutanix&#xff08;路坦力&#xff09;宣布中国市场自 2023 财年起将转型为合作伙伴销售主导模式&#xff0c;引起了广泛关注&#xff1b;同时结合当前 IT 基础架构的国产化趋势背景&#xff0c;不少正在使用和考虑使用 Nutanix 产品的企业开始…

js 跨域访问问题解决方法

什么引起了ajax不能跨域请求的问题&#xff1f; ajax本身实际上是通过XMLHttpRequest对象来进行数据的交互&#xff0c;而浏览器出于安全考虑&#xff0c;不允许js代码进行跨域操作&#xff0c;所以会警告。 有什么完美的解决方案么&#xff1f; 解决方案有不少&#xff0c;但…

数字化门店管理|如何让门店数字化管理,更加贴合日常运营细节?

在赋能品牌门店数字化管理的过程中&#xff0c;帷幄既注重前沿 AI 算法带来的技术驱动力&#xff0c;也注重基于门店管理中的真实场景与需求&#xff0c;让算法更贴合业务实际需求&#xff0c;从而带来运营优化与降本增效。 1 月&#xff0c;「帷幄数智空间 Whale SpaceSight」…

植物大战 动态内存——C++

这里是目录标题前言动态内存分布如何理解C语法的增加newnew用法关于struct和class的使用关于free和delete的区别。背会这句话抛异常operator new和operator delete内存池new和delete原理定位newmalloc和new的区别是什么&#xff1f;内存泄漏前言 总结复习前面的知识。 注意&a…