Bootstrap

总结:HBase实践篇

一、命令行操作

一般我们都是通过DFS操作HBase,基本不会通过shell的方式,以下主要是了解下底层操作原理吧。

1、进入HBase环境:./bin/hbase shell

2、创建表与插入数据:

# create语法
create '表名', {NAME => '列族名1'}, {NAME => '列族名2'}, {NAME => '列族名3'}
# 此种方式是上上面的简写方式,使用上面方式可以为列族指定更多的属性,如VERSIONS、TTL、BLOCKCACHE、CONFIGURATION等属性
create '表名', '列族名1', '列族名2', '列族名3'
hbase(main):003:0> create 'test', 'cf'
0 row(s) in 1.2200 seconds
hbase(main):003:0> list 'table'
test
1 row(s) in 0.0550 seconds
hbase(main):004:0> put 'test', 'row1', 'cf:a', 'value1'
0 row(s) in 0.0560 seconds
hbase(main):005:0> put 'test', 'row2', 'cf:b', 'value2'
0 row(s) in 0.0370 seconds
hbase(main):006:0> put 'test', 'row3', 'cf:c', 'value3'
0 row(s) in 0.0450 seconds

3、put:添加或修改的表的值。

类似mysql的insert语句,案例如下:

# 语法
# 当列族中只有一个列时'列族名:列名'使用'列族名'
put '表名', '行键', '列族名', '列值'
put '表名', '行键', '列族名:列名', '列值'

# 示例

# 创建表
create 'tbl_user', 'info', 'detail', 'address'

# 第一行数据
put 'tbl_user', 'mengday', 'info:id', '1'
put 'tbl_user', 'mengday', 'info:name', '张三'
put 'tbl_user', 'mengday', 'info:age', '28'

put 'tbl_user', 'mengday', 'detail:birthday', '1990-06-26'
put 'tbl_user', 'mengday', 'detail:email', '[email protected]'
put 'tbl_user', 'mengday', 'detail:create_time', '2019-03-04 14:26:10'

put 'tbl_user', 'mengday', 'address', '上海市'

# 第二行数据
put 'tbl_user', 'vbirdbest', 'info:id', '2'
put 'tbl_user', 'vbirdbest', 'info:name', '李四'
put 'tbl_user', 'vbirdbest', 'info:age', '27'

put 'tbl_user', 'vbirdbest', 'detail:birthday', '1990-06-27'
put 'tbl_user', 'vbirdbest', 'detail:email', '[email protected]'
put 'tbl_user', 'vbirdbest', 'detail:create_time', '2019-03-05 14:26:10'

put 'tbl_user', 'vbirdbest', 'address', '北京市'


# 第一行数据
put 'tbl_user', 'xiaoming', 'info:id', '3'
put 'tbl_user', 'xiaoming', 'info:name', '王五'
put 'tbl_user', 'xiaoming', 'info:age', '26'

put 'tbl_user', 'xiaoming', 'detail:birthday', '1990-06-28'
put 'tbl_user', 'xiaoming', 'detail:email', '[email protected]'
put 'tbl_user', 'xiaoming', 'detail:create_time', '2019-03-06 14:26:10'

put 'tbl_user', 'xiaoming', 'address', '杭州市'

4、scan:扫描表信息

通过对表的扫描来获取对用的值

scan ‘表名’
扫描某个列族: scan ‘表名’, {COLUMN=>‘列族名’}
扫描某个列族的某个列: scan ‘表名’, {COLUMN=>‘列族名:列名’}
查询同一个列族的多个列: scan ‘表名’, {COLUMNS => [ ‘列族名1:列名1’, ‘列族名1:列名2’, …]}

# 扫描命名空间hbase下的meta表,显示出meta表的所有数据
  hbase> scan 'hbase:meta'

# 扫描命名空间hbase下的meta表的列族info的列regioninfo,显示出meta表的列族info下的regioninfo列的所有数据
  hbase> scan 'hbase:meta', {COLUMNS => 'info:regioninfo'}

# 扫描命名空间ns1下表t1的列族'c1'和'c2'。显示出命名空间ns1下表t1的列族'c1'和'c2'的所有数据
     hbase> scan 'ns1:t1', {COLUMNS => ['c1', 'c2']}

# 扫描命名空间ns1下表t1的列族'c1'和'c2'。显示出命名空间ns1下表t1的列族'c1'和'c2',且只显示前10个rowkey的数据。
  hbase> scan 'ns1:t1', {COLUMNS => ['c1', 'c2'], LIMIT => 10}

# 扫描命名空间ns1下表t1的列族'c1'和'c2'。显示出命名空间ns1下表t1的列族'c1'和'c2',且只显示从rowkey=“xyz”开始的前10个rowkey的数据。
  hbase> scan 'ns1:t1', {COLUMNS => ['c1', 'c2'], LIMIT => 10, STARTROW => 'xyz'} 

# 扫描默认命名空间下表t1的列族c1时间戳从'1303668804'到'1303668904'的数据
  hbase> scan 't1', {COLUMNS => 'c1', TIMERANGE => [1303668804, 1303668904]}

# 反向显示表t1的数据
  hbase> scan 't1', {REVERSED => true}

# 过滤显示表t1的数据
  hbase> scan 't1', {FILTER => "(PrefixFilter ('row2') AND
    (QualifierFilter (>=, 'binary:xyz'))) AND (TimestampsFilter ( 123, 456))"}

# RAW为true,显示出表t1的所有数据,包括已经删除的
  hbase> scan 't1', {RAW => true, VERSIONS => 10}

# 表t1的引用的扫描
  hbase> t11 = get_table 't1'
  hbase> t11.scan

5、get

#得到命名空间ns1下表t1的rowkey为r1的数据
  hbase> get 'ns1:t1', 'r1'

#得到默认命名空间下表t1的rowkey为r1的数据
  hbase> get 't1', 'r1'

#得到默认命名空间下表t1的rowkey为r1,时间戳范围在ts1和ts2之间的数据
  hbase> get 't1', 'r1', {TIMERANGE => [ts1, ts2]}

#得到默认命名空间下表t1的rowkey为r1的c1列的数据
  hbase> get 't1', 'r1', {COLUMN => 'c1'}

#得到默认命名空间下表t1的rowkey为r1的c1,c2,c3列的数据
  hbase> get 't1', 'r1', {COLUMN => ['c1', 'c2', 'c3']}

#得到默认命名空间下表t1的rowkey为r1的c1列,时间戳为ts1的数据
  hbase> get 't1', 'r1', {COLUMN => 'c1', TIMESTAMP => ts1}

#得到默认命名空间下表t1的rowkey为r1的c1列,时间戳范围为ts1到ts2,版本数为4的数据
  hbase> get 't1', 'r1', {COLUMN => 'c1', TIMERANGE => [ts1, ts2], VERSIONS => 4}

#应用对象的用法
  hbase> t.get 'r1'
  hbase> t.get 'r1', {TIMERANGE => [ts1, ts2]}
  hbase> t.get 'r1', {COLUMN => 'c1'}
  hbase> t.get 'r1', {COLUMN => ['c1', 'c2', 'c3']}
  hbase> t.get 'r1', {COLUMN => 'c1', TIMESTAMP => ts1}
  hbase> t.get 'r1', {COLUMN => 'c1', TIMERANGE => [ts1, ts2], VERSIONS => 4}
  hbase> t.get 'r1', {COLUMN => 'c1', TIMESTAMP => ts1, VERSIONS => 4}

6、count 'hubble_uid'

二、代码操作

1、连接HBase

@Before
public void init() throws Exception {
		config = HBaseConfiguration.create();// 配置
		config.set("hbase.zookeeper.quorum", "192.168.33.61");// zookeeper地址
		config.set("hbase.zookeeper.property.clientPort", "2181");// zookeeper端口
		connection = ConnectionFactory.createConnection(config);
		table = connection.getTable(TableName.valueOf("dept"));
}

也可以将zookeeper地址放到hdfs-site.xml中,如:

<property>
  <name>hbase.zookeeper.property.clientPort</name>
  <value>2181</value>
  <description>Property from ZooKeeper's config zoo.cfg. The port at which the clients will connect</description>
</property>

<property>
  <name>hbase.zookeeper.quorum</name>
  <value>hbase-zookeeper01-zjy.xx.hadoop,hbase-zookeeper02-zjy.xx.hadoop</value>
  <description>Comma separated full list of servers in the ZooKeeper Quorum </description>
</property>

2、身份认证

并不是谁都可以访问HBase服务进行操作的,所以要有安全认证规范。

可采用Kerberos 身份认证(比如我们使用的keytab就是Kerberos的身份认证方式)

Kerberos 是一种基于对称密钥技术的身份认证协议,它作为一个独立的第三方的身份认证服务,可以为其它服务提供身份认证功能,且支持 SSO。

Kerberos认证支持两种方式:密码认证及keytab认证。认证有效时间默认为24小时。

密码认证:通过输入用户正确的密码完成身份认证。主要在运维管理场景中使用“人机”用户进行认证,命令为kinit 用户名。
keytab认证:keytab文件包含了用户的安全信息。使用keytab文件认证时,系统自动使用加密的凭据信息进行认证无需输入用户密码。主要在组件应用开发场景中使用,且使用“机机”用户。keytab文件也支持在kinit命令中使用。
 

如何指定使用什么身份认证?

在hdfs-site.xml中配置:

<property>
  <name>hbase.security.authentication</name>
  <value>kerberos</value>
</property>

3、写数据

大致有四步操作

3.1、第一步:构建数据列表

List<Put> puts = new ArrayList<>();

3.2、第二步:构建PUT元组,返回类型为Put

/**
	 * 构建put元组 a.rokey格式: MD5 + timestamp b.tagkey 按照字母顺序升序排列
	 * 例:5470|1548316800|localhost.localdomain.4c4c4544-0053-5710-8043-b3c04f505232|df.bytes.total|fstype=xfs|group=ATS_live|idc=chengdu3_scc|mount=/|step=60,value:598729555968
	 *
	 * @param nodeMap
	 *            数据集
	 * @return Put
	 */
	private static Put build(Map<String, Object> nodeMap) {
		Map<String, Object> itemMap = new TreeMap<String, Object>() {
			private static final long serialVersionUID = -2149400882864567718L;
		};
		// Map<String, String> itemset= new TreeMap<String, String>(){};
		String value = null;
		// 后期这里会变成1分钟1次的心跳和mysql获取数据的
		String ignoreWord = ",timestamp,value,counterType,valueStr,"; // ,group,idc
		Iterator<String> nodeIterator = nodeMap.keySet().iterator();
		while (nodeIterator.hasNext()) {
			String key = nodeIterator.next();
			if (ignoreWord.indexOf("," + key + ",") == -1) {
				if (key.contains(".")) {
					Object val = nodeMap.get(key);
					key = key.replace(".", "|_|_|");
					itemMap.put(key, val);
				} else {
					itemMap.put(key, nodeMap.get(key));
				}

			}
		}

		String mD5Str = DigestUtils.md5Hex(itemMap.toString());

		String timestamp = String.valueOf(nodeMap.get("timestamp"));

		if (nodeMap.containsKey("valueStr")) {
			value = String.valueOf(nodeMap.get("valueStr"));
			logger.debug("valueStr :{}", value);
			if (value.length() > 100) {
				value = value.substring(0, 100);
			}
		} else {
			value = String.valueOf(nodeMap.get("value"));
		}

		String timehour = String.valueOf(Long.parseLong(timestamp) / 3600 * 3600);
		String timesecond = String.valueOf(Long.parseLong(timestamp) % 3600);

		StringBuffer rowkeyBuffer = new StringBuffer();
		rowkeyBuffer.append(mD5Str);
		rowkeyBuffer.append("|");
		rowkeyBuffer.append(timehour);

		logger.debug("MD5 Str is {}", itemMap.toString());
		logger.debug("build rowkeyBuffer:{},timesecond:{},value:{}", rowkeyBuffer.toString(), timesecond, value);
		byte[] rowKeyArray = Bytes.toBytes(rowkeyBuffer.toString());
		Put put = null;

		put = new Put(rowKeyArray);

		put.addColumn(Bytes.toBytes(CF), Bytes.toBytes(timesecond), Bytes.toBytes(value));
		return put;
	}

3.3、第三步:填充puts数据

第一步生成的Put元组add到list中

3.4、第四步:数据批量写入

/**
     * 往指定表添加数据 , 返回成功的个数和总数
     * @param tablename 表名
     * @param puts      需要添加的数据
     * @return long                返回执行时间
     * @throws IOException
     */
    public static void putByHTableSuccessful(String tablename, List<?> puts) throws Exception {
        long currentTime = System.currentTimeMillis();
        Connection conn = getConnection();
        HTable htable = (HTable) conn.getTable(TableName.valueOf(tablename));
        htable.setAutoFlushTo(false);//是否自动刷新提交
        htable.setWriteBufferSize(5 * 1024 * 1024);
        Integer successNum = 0;
        Integer allNum = 0;
        try {
            htable.put((List<Put>) puts);
            htable.flushCommits();
            successNum = puts.size();
        } finally {
            htable.close();
            allNum = puts.size();
        }
    }

通过调用HTable.setAutoFlushTo(false)方法可以将HTable写客户端自动flush关闭,这样可以批量写入数据到HBase,而不是有一条put就执行一次更新,只有当put填满客户端写缓存的时候,才会向HBase服务端发起写请求。默认情况下auto flush是开启的。
 

4、读数据

//第一步:由于是scan查询,需要先组织好开始rowkey和结束的rowkey
String prefixRow = md5Id.toString();
String startRow = prefixRow + "|"  +  starthourString;
String stopRow = prefixRow + "|" + endhourString;
String tablename = "XXX";

//第二步:
public static TreeMap<Integer, String> scanPrefixgetMap(String tablename, String prefixRow, String startRow,
			String StopRow, String filterOperation, String filterValue) throws IOException {
        //定义扫描的表
		Table table = null;
        //扫描得到的数据
		ResultScanner resultScanners = null;
        //封装数据给client
		TreeMap<Integer, String> timestampValue = new TreeMap<Integer, String>();
		try {
            //获取表
			table = conn.getTable(TableName.valueOf(tablename));

            //扫描条件封装
			Scan scan = new Scan();
			byte[] startRowBytes = Bytes.toBytes(startRow);
			byte[] stopRowBytes = Bytes.toBytes(StopRow);
			byte[] prefixRowBytes = Bytes.toBytes(prefixRow);
			if (filterOperation != null) {
				Filter filter = null;
				byte[] filterValueByte = Bytes.toBytes(filterValue);
				switch(filterOperation){
				case "!=":
					filter = new ValueFilter(CompareOp.NOT_EQUAL,
							new BinaryComparator(filterValueByte));
					scan.setFilter(filter);
					break;
				case "=":
					filter = new ValueFilter(CompareOp.EQUAL,
							new BinaryComparator(filterValueByte));
					scan.setFilter(filter);
					break;
				case "<":
					filter = new ValueFilter(CompareOp.LESS,
							new BinaryComparator(filterValueByte));
					scan.setFilter(filter);
					break;
				case ">":
					filter = new ValueFilter(CompareOp.GREATER,
							new BinaryComparator(filterValueByte));
					scan.setFilter(filter);
					break;
				}
			}
			
			scan.setRowPrefixFilter(prefixRowBytes);
			scan.setStartRow(startRowBytes);
			scan.setStopRow(stopRowBytes);
			scan.setCaching(1024 * 5);

            //扫描核心方法 - 核心方法
			resultScanners = table.getScanner(scan);

            //数据转换
			timestampValue = transferResults(resultScanners);
			resultScanners.close();

			startRowBytes = null;
			stopRowBytes = null;
			prefixRowBytes = null;
		} catch (IOException e) {
			LOGGER.error(String.format("HbaseUtil.scanPrefixRow has error, tablename = {}, prefixRow = {}", tablename,
					prefixRow), e);
		} finally {
			if (table != null) {
				table.close();
			}
		}
		return timestampValue;
	}


三、HBase配置文件

参考:HBase配置文件详解(一) - 简书

1、配置文件

HBase使用与Hadoop相同的配置系统,所有配置文件都位于 conf/ 目录中,需要保持群集中每个节点的同步。

配置文件说明

  • backup-masters - 这是一个纯文本文件,其中列出了主服务器应在其上启动备份主进程的主机列表,每行一台主机名(默认情况下不存在
  • hadoop-metric2-hbase.properties - 用于连接HBase和Hadoop的Metric2框架,默认情况下只包含注释出的示例
  • /hbase-env.cmd/hbase-env.sh - 用于Windows和Linux/Unix环境的脚本,用于设置HBase的工作环境,包括 JAVA和其他环境变量的配置(该文件包含许多注释示例来提供指导,该文件的改动需要重启HBase才能生效)
  • hbase-policy.xml - RPC服务器使用默认策略配置文件对客户端请求进行授权决策(仅在启用HBase安全性的情况下使用
  • hbase-site.xml - 主要的HBase配置文件,该文件覆盖了HBase的默认配置的配置选项(可以在HBase Web UI的HBase配置选项中查看整个集群的有效配置,包括默认和覆盖的)
  • log4j.properties - HBase日志记录的配置文件
  • regionservers - 纯文本文件,包含在HBase集群中运行RegionSever的主机名或IP列表,每行一个(默认情况下,这个文件包含单条条目 localhost

2、重要属性

用于启动HBase:

  • hbase.tmp.dir本地文件系统 的临时文件夹(重新启动计算机将清空 /tmp目录)
    • 类型:string
    • 默认值:${java.io.tmpdir}/hbase-${user.name}
  • hbase.rootdir:这个目录是region服务器共享的目录,用来持久存储HBase的数据,URL必须完全正确,其中包含了文件系统的schema。例如,要表示HDFS中的/hbase目录,HDFS实例的namenode需要运行在服务器 namenode.example.org 的9000端口,且需要将这个属性设置为 hdfs://namenode.example.org:9000/hbase。默认情况下,HBase是写到/tmp下的,如果不修改这个配置,数据将会在集群重启时丢失。
    • 类型:string
    • 默认值:${hbase.tmp.dir}/hbase
  • hbase.cluster.distributed:HBase集群的运行模式,该值为 false 时,集群为单机模式;该值为 true 时,集群是分布式模式。如果将该值设置为false,则HBase与Zookeeper的守护进程将运行在一个JVM中。
    • 类型:boolean
    • 默认值:false
  • hbase.zookeeper.quorumZookeeper Quorum中的服务器列表,使用逗号分隔。默认设置为“host1.mydomain.com,host2.mydomain.com,host3.mydomain.com”,协助伪分布式模式使用。在完全分布式模式下,用户需要把所有Zookeeper Quorum节点添加进去,如果在 hbase-env.sh 文件中设置了 HBASE_MANAGES_ZK变量,则此列表中的节点就是我们启动或停止Zookeeper服务的节点。
    • 类型:string
    • 默认值:localhost

;