hbase使用 + api + protobuf + 优化

目录

各个角色的任务:

Client

Zookeeper

HMaster:

RegionServer

Region

store

启动问题:

hbase客户端使用

A.创建表 + 创建库

B.插入数据

c. 查询数据

D.删除表: 

E. flush

F.清除表中的数据

pom文件配置

Hbase api使用

Hbase protobuf使用

A.安装软件组

B.官网下载 protobuf

C. 生成java 文件 

D. 使用生成的java文件

Hbase 优化

A.表设计

1. 对Regin 进行分区

2. rowkey尽可能短,减少保存的数据的大小

3.列簇的个数最多为  2 个

4.版本号 

5.数据过期时间 time-to-live

6.compact 操作在系统闲暇的时候进行操作

B.写操作

1. 多HTable并发写

2.Auto Flush

3.Write Buffer

4.WAL Flag(开启)

5.批量写

C.读操作

1.多HTable并发读

2.Scanner Caching

3.Scan Attribute Selection

4.Close ResultScanner

5.批量读

6.缓存查询结果缓存查询结果

7. Blockcache

D.HTablePool解决HTable存在的线程不安全问题

问题

1.分组 development 不包含任何可安装软件包。

2.Please use 'syntax = "proto2";' or 'syntax = "proto3";' to specify a syntax version. (Defaulted to proto2 syntax. 

3.InvalidProtocolBufferException: Message missing required fields: length, type, date


 

各个角色的任务:

Client

包含访问HBase的接口并维护cache来加快对HBase的访问

Zookeeper

保证任何时候,集群中只有一个master

存贮所有Region的寻址入口

实时监控Region server的上线和下线信息

并实时通知Master 存储HBase的schema和table元数据

HMaster:

Master 为Region server分配region

负责Region server的负载均衡

发现失效的Region server并重新分配其上的region

管理用户对table的增删改操作 

RegionServer

维护region,处理对这些region的IO请求

负责切分在运行过程中变得过大的region

Region

HBase自动把表水平划分成多个区域(region),每个region会保存一个表里面某段连续的数据

每个表一开始只有一个region,随着数据不断插入表,region不断增大,当增大到一个阀值的时候,region就会等分会两个新的region(裂变) 当table中的行不断增多,就会有越来越多的region。这样一张完整的表被保存在多个Regionserver 上

一个region由多个store组成,

store

一个store对应一个CF(列族)

store分为:1.内存中的memstore     2.位于磁盘的storefile

写操作先写入memstore,当memstore中的数据达到某个阈值,regionserver会启动flashcache进程写入storefile,每次写入形成单独的一个storefile

当storefile文件的数量增长到一定阈值后,系统会进行合并(minor、major compaction),

在合并过程中会进行版本合并和删除工作(majar),形成更大的storefile

当一个region所有storefile的大小和数量超过一定阈值后,会把当前的region分割为两个,并由hmaster分配到相应的regionserver服务器,实现负载均衡

客户端检索数据,先在memstore找,找不到再找storefile

 

启动问题:

进行hbase 再次启动的时候,发现 master 节点是100,备份节点是 101

但是在设置的时候设置的时候,指定的是 hdfs集群,reginserve节点,备份节点100

在作为master 节点的那个节点启动 start-hbase.sh 但是显示的主节点还是 100,

我手动将 hbase 服务关闭,然后开启,发现显示正常了

 

 

hbase客户端使用

A.创建表 + 创建库

通过 help 可以查看hbase 支持的命令

创建命名空间(相当于mysql 的库名称)

创建表如果不指定是那一库,则默认是 default,该表的名称为 psn, 拥有两个列簇 cf1 和 cf2

create 'psn','cf1','cf2'

指定 命名空间,创建表

create 'yang:user','cf1','cf2'

 

B.插入数据

在插入数据的时候,不指定是哪一个库名称, 默认是插入default 库中表的数据

hbase 中一行数据的唯一标识为 rowkey , 一个列簇中可以有无数个属性

-- rowkey 为 1111, cf1列簇中 name 属性的值为 xiaoming
put 'psn','111','cf1:name','xiaoming'

-- rowkey 为 1111, cf1列簇中 age 属性的值为 21
put 'psn','111','cf1:age','21'

-- rowkey 为 222, cf1列簇中 phone 属性的值为 13322223333
put 'psn','222','cf1:phone','13322223333'

-- rowkey 为 333, cf2列簇中 address 属性的值为 地球,中国,河南
put 'psn','333','cf2:address','地球,中国,河南'

 指定插入的库

-- yang 库中 user 表, rowkey=111 , cf1列簇中 name 属性的值为 admin
put 'yang:user','111','cf1:name','admin'

-- yang 库中 user 表, rowkey=111 , cf1列簇中 pwd 属性的值为 admin
put 'yang:user','111','cf1:pwd','admin'

 

c. 查询数据

scan 对表进行扫描,输出全部的数据

scan 'psn'

获得某一行的数据 

get 'psn','111'

获得某一行,中某个列簇中某个属性的数据

get 'psn','111','cf1:age'

查询某个库中某个表的数据

get 'yang:user','111','cf1:name'

D.删除表: 

在使用 drop '表名'  的时候,提示必须要先将表设置为不可用 

通过 help “关键字”  可以查看该命令的语法

将表设置不可用   

查看表的状态 显示为不可用

describe 'psn'

删除表

删除库的时候必须要保证该库是空的才能记性删除操作

-- 将表设置为不可用
disable 'yang:user'

-- 删除表
drop 'yang:user'

-- 删除库
drop_namespace 'yang'

-- 查询空间名称
list_namespace

 

E. flush

在向表中插入数据的时候,数据是先存储在 memstore 内存中的,在内存中的数据达到一定的值后,会进行溢写操作

在对表进行 flush 操作后,可以在 psn 文件夹中找到hbase 的 store-file 文件 

查看 psn 表的 cf1 类簇中 466d5c970d4a47099cdc1d0c513a83d6 文件的数据

hbase hfile -p -f /hbase/data/default/psn/ac5571e0c2bb0ea45fea99372b65bd4f/cf1/466d5c970d4a47099cdc1d0c513a83d6

 

F.清除表中的数据

truncate 'psn'

 

 

pom文件配置

选中的hadoop ,hive,hbase ,protobuf 的jar 包版本一定要和安装软件的时候 jar 包版本一致,否则可能会出现jar 包找不到的情况

<dependencies>
        <!-- hadoop start -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>3.2.1</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>3.2.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>3.2.1</version>
        </dependency>

        <!-- hive start -->
        <dependency>
            <groupId>org.apache.hive</groupId>
            <artifactId>hive-jdbc</artifactId>
            <version>3.1.2</version>
            <exclusions>
                <exclusion>
                    <groupId>org.eclipse.jetty.aggregate</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--<dependency>
            <groupId>org.apache.hive</groupId>
            <artifactId>hive-exec</artifactId>
            <version>3.1.2</version>
        </dependency>-->

        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.el</artifactId>
            <version>3.0.1-b06</version>
        </dependency>

        <!-- protobuf start -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.13.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java-util</artifactId>
            <version>3.13.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId> hbase-shaded-client</artifactId>
            <version>2.3.1</version>
        </dependency>

    </dependencies>

 

 

Hbase api使用

1.初始化

2. 创建表

3.插入数据

4.查询数据(a.根据rowkey查询,b.根据rowkey前缀查询, c.根据rowkey前缀+类簇某个属性查询,d.查询该字段所有版本的数据)

5.关闭连接


public class HBaseDemo {

	Admin admin;
	Table table;
	String TN = "phone";
	Connection connection;

	@Before
	public void init() throws Exception {
		Configuration configuration = HBaseConfiguration.create();
		/* 这里填写 zookeeper 集群的 ip */
		configuration.set("hbase.zookeeper.quorum", "yang101,yang102,yang103");
		connection = ConnectionFactory.createConnection(configuration);
		admin = connection.getAdmin();
		table = connection.getTable(TableName.valueOf(TN));
	}

	@Test
	public void creatTable() throws Exception {

		TableName tableName = TableName.valueOf(TN);
		if (admin.tableExists(tableName)) {
			//删除表
			admin.disableTable(tableName);
			admin.deleteTable(tableName);
		}

		// 表描述
		HTableDescriptor desc = new HTableDescriptor(tableName);
		HColumnDescriptor cf = new HColumnDescriptor("cf".getBytes());
		desc.addFamily(cf);
		admin.createTable(desc);
	}


	@Test
	public void insertDB() throws Exception {
		String rowKey = "1231231312";
		Put put = new Put(rowKey.getBytes());
		put.addColumn("cf".getBytes(), "name".getBytes(), "xiaohong".getBytes());
		put.addColumn("cf".getBytes(), "age".getBytes(), "23".getBytes());
		put.addColumn("cf".getBytes(), "sex".getBytes(), "women".getBytes());
		table.put(put);
	}

	/**
	 * 通过 rowkey 查询数据
	 */
	@Test
	public void getDB() throws Exception {
		String rowKey = "1231231312";
		Get get = new Get(rowKey.getBytes());
		get.addColumn("cf".getBytes(), "name".getBytes());
		get.addColumn("cf".getBytes(), "age".getBytes());
		get.addColumn("cf".getBytes(), "sex".getBytes());
		get.addColumn("cf".getBytes(), "phone".getBytes());
		Result rs = table.get(get);
		Cell cell = rs.getColumnLatestCell("cf".getBytes(), "name".getBytes());
		Cell cell2 = rs.getColumnLatestCell("cf".getBytes(), "age".getBytes());
		Cell cell3 = rs.getColumnLatestCell("cf".getBytes(), "sex".getBytes());
		Cell cell4 = rs.getColumnLatestCell("cf".getBytes(), "phone".getBytes());

		System.out.println(new String(CellUtil.cloneValue(cell)));
		System.out.println(new String(CellUtil.cloneValue(cell2)));
		System.out.println(new String(CellUtil.cloneValue(cell3)));
		System.out.println(new String(CellUtil.cloneValue(cell4)));

	}

	/**
	 * 扫描表中的全部数据
	 */
	@Test
	public void scanPhone() throws IOException {
		Scan scan = new Scan();
		ResultScanner resutScanner = table.getScanner(scan);
		for(Result result: resutScanner){
			Cell cell = result.getColumnLatestCell("cf".getBytes(), "name".getBytes());
			Cell cell2 = result.getColumnLatestCell("cf".getBytes(), "age".getBytes());
			Cell cell3 = result.getColumnLatestCell("cf".getBytes(), "sex".getBytes());

			System.out.print(new String(CellUtil.cloneValue(cell)) + "  ");
			System.out.print(new String(CellUtil.cloneValue(cell2)) + "  ");
			System.out.println(new String(CellUtil.cloneValue(cell3)));
		}
	}

	SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");

	/**
	 * 有10个用户,每个用户随机产生100条记录
	 * 
	 * @throws Exception
	 */
	@Test
	public void insertDB2() throws Exception {
		List<Put> puts = new ArrayList<Put>();
		for (int i = 0; i < 10; i++) {
			String phoneNum = getPhoneNum("186");
			for (int j = 0; j < 100; j++) {
				String dnum = getPhoneNum("158");
				String length = r.nextInt(99) + "";
				//拨打电话为1, 接听电话为0
				String type = r.nextInt(2) + "";
				String dateStr = getDate("2020");

				String rowkey = phoneNum + "_" + (Long.MAX_VALUE - sdf.parse(dateStr).getTime());

				Put put = new Put(rowkey.getBytes());
				put.addColumn("cf".getBytes(), "dnum".getBytes(), dnum.getBytes());
				put.addColumn("cf".getBytes(), "length".getBytes(), length.getBytes());
				put.addColumn("cf".getBytes(), "type".getBytes(), type.getBytes());
				put.addColumn("cf".getBytes(), "date".getBytes(), dateStr.getBytes());

				puts.add(put);
			}
		}

		table.put(puts);
	}

	/**
	 * 通过指定 rowkey 的范围获得数据
	 *
	 * @throws Exception
	 */
	@Test
	public void scan() throws Exception {
		//产生 rowkey
		String phoneNum = "18694881974";
		String startRow = phoneNum + "_" + (Long.MAX_VALUE - sdf.parse("20200801000000").getTime());
		String stopRow = phoneNum + "_" + (Long.MAX_VALUE - sdf.parse("20200201000000").getTime());
		Scan scan = new Scan();
		scan.setStartRow(startRow.getBytes());
		scan.setStopRow(stopRow.getBytes());
		ResultScanner rss = table.getScanner(scan);
		for (Result rs : rss) {
			System.out
					.print(new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "dnum".getBytes()))));
			System.out.print("-"
					+ new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "length".getBytes()))));
			System.out.print(
					"-" + new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "type".getBytes()))));
			System.out.println(
					"-" + new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "date".getBytes()))));
		}
	}

	/**
	 * rowkey开头的行
	 * 列簇某个属性
	 * 查询某个手机号 主叫为1 的所有记录
	 *
	 * @throws Exception
	 */
	@Test
	public void scan2() throws Exception {
		FilterList list = new FilterList(FilterList.Operator.MUST_PASS_ALL);
		//以 18694881974 开头所有rowkey 行的数据
		PrefixFilter filter1 = new PrefixFilter("18694881974".getBytes());
		// type 的值为 1 的数据
		SingleColumnValueFilter filter2 = new SingleColumnValueFilter("cf".getBytes(), "type".getBytes(),
				CompareOp.EQUAL, "1".getBytes());
		list.addFilter(filter1);
		list.addFilter(filter2);
		Scan scan = new Scan();
		scan.setFilter(list);
		ResultScanner rss = table.getScanner(scan);
		for (Result rs : rss) {
			System.out
					.print(new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "dnum".getBytes()))));
			System.out.print("-"
					+ new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "length".getBytes()))));
			System.out.print(
					"-" + new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "type".getBytes()))));
			System.out.println(
					"-" + new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "date".getBytes()))));
		}
	}

	/**
	 * 186开头的rowkey的行
	 * @throws Exception
	 */
	@Test
	public void scan3() throws Exception {
		FilterList list = new FilterList(FilterList.Operator.MUST_PASS_ALL);
		PrefixFilter filter1 = new PrefixFilter("186".getBytes());
		list.addFilter(filter1);
		Scan scan = new Scan();
		scan.setFilter(list);
		ResultScanner rss = table.getScanner(scan);
		for (Result rs : rss) {
			System.out
					.print(new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "dnum".getBytes()))));
			System.out.print("-"
					+ new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "length".getBytes()))));
			System.out.print(
					"-" + new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "type".getBytes()))));
			System.out.println(
					"-" + new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "date".getBytes()))));
		}
	}

	/**
	 * 列簇某个属性,所有时间节点的数据
	 */
	@Test
	public void scan4() throws IOException {
		Get get = new Get(Bytes.toBytes("18688662873_9223370447081157807"));
		get.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("length")); // 获取指定列族和列修饰符对应的列
		Result result = table.get(get);
		for (Cell kv : result.listCells()) {
			System.out.println("family:" + Bytes.toString(kv.getFamilyArray()));
			System.out.println("qualifier:" + Bytes.toString(kv.getQualifierArray()));
			System.out.println("value:" + Bytes.toString(kv.getValueArray()));
			System.out.println("Timestamp:" + kv.getTimestamp());
			System.out.println("-------------------------------------------");
		}

	}



	private String getDate(String year) {
		return year + String.format("%02d%02d%02d%02d%02d",
				new Object[] { r.nextInt(12) + 1, r.nextInt(31) + 1, r.nextInt(24), r.nextInt(60), r.nextInt(60) });
	}

	private String getDate2(String yearMonthDay) {
		return yearMonthDay
				+ String.format("%02d%02d%02d", new Object[] { r.nextInt(24), r.nextInt(60), r.nextInt(60) });
	}

	Random r = new Random();

	/**
	 * 生成随机的手机号码
	 * 
	 * @param string
	 * @return
	 */
	private String getPhoneNum(String string) {
		return string + String.format("%08d", r.nextInt(99999999));
	}


	@After
	public void destory() throws Exception {
		if (admin != null) {
			admin.close();
		}
	}
}

 

Hbase protobuf使用

protobuf 类似于 java 中序列化功能的软件,能够支持 C++、Java、Python 三种语言

A.安装软件组

查看系统可用的软件组

yum groups list 

查看软件组的详细信息,都有哪些软件

yum groups info 开发工具

进行安装开发工具,

yum groups install 开发工具 -y     

B.官网下载 protobuf

https://developers.google.cn/protocol-buffers/docs/downloads

拉到页面的最下面,会看到一些软件的版本, 

进行解压,然后进入到文件夹内

找到 protoc 可执行文件 

whereis protoc

修改  /etc/profile 文件

 

C. 生成java 文件 

新建 phone.proto 文件, 该文件的后缀 必须为 .proto

vim phone.proto

proto 的版本分为 2 和 3

2版本,必须要写 字段修饰符 

3版本,不能有 字段修饰符,这些在进行文件生成的时候会进行提示(当不符合版本要求的时候)

syntax = "proto2";
package com.sys.hbase;

message PhoneDetail {
  required string dnum=1;  // 手机号码
  required string length=2;  // 通话时长
  required string  type=3;  // 类型 0-接听;1-拨出 
  required string date=4; // 动作时间

}
message dayPhoneDetail
{
   repeated PhoneDetail dayPhoneDetail = 1;
}

生成 java 文件

protoc --java_out=./ phone.proto 

生成了一个 指定路径的 java 文件

文件下载到本地

D. 使用生成的java文件

1.初始化

2. 创建表

3.插入数据

4.查询数据(a.根据rowkey前缀)

5.关闭连接


public class HBaseDemoProtobuf {

	Admin admin;
	Table table;
	String TN = "phone";
	Connection connection;

	@Before
	public void init() throws Exception {
		Configuration configuration = HBaseConfiguration.create();
		/* 这里填写 zookeeper 集群的 ip */
		configuration.set("hbase.zookeeper.quorum", "yang101,yang102,yang103");
		connection = ConnectionFactory.createConnection(configuration);
		admin = connection.getAdmin();
		table = connection.getTable(TableName.valueOf(TN));
	}

	@Test
	public void creatTable() throws Exception {

		TableName tableName = TableName.valueOf(TN);
		if (admin.tableExists(tableName)) {
			//删除表
			admin.disableTable(tableName);
			admin.deleteTable(tableName);
		}

		// 表描述
		HTableDescriptor desc = new HTableDescriptor(tableName);
		HColumnDescriptor cf = new HColumnDescriptor("cf".getBytes());
		desc.addFamily(cf);
		admin.createTable(desc);
	}

    /**
	 * 插入数据
	 */
	@Test
	public void insertDB3() throws Exception {
		List<Put> puts = new ArrayList<Put>();
		for (int i = 0; i < 10; i++) {
			String phoneNum = getPhoneNum("182");
			for (int j = 0; j < 100; j++) {
				String dnum = getPhoneNum("158");
				String length = r.nextInt(99) + "";
				String type = r.nextInt(2) + "";
				String dateStr = getDate("2020");
				String rowkey = phoneNum + "_" + (Long.MAX_VALUE - sdf.parse(dateStr).getTime());
				Phone.PhoneDetail.Builder phoneDetail = Phone.PhoneDetail.newBuilder();
				phoneDetail.setDate(dateStr);
				phoneDetail.setDnum(dnum);
				phoneDetail.setLength(length);
				phoneDetail.setType(type);
				Put put = new Put(rowkey.getBytes());
				put.addColumn("cf".getBytes(), "phoneDetail".getBytes(), phoneDetail.build().toByteArray());
				puts.add(put);
			}
		}
		table.put(puts);
	}

   /**
	 * 查询数据 不同的解析对象 Phone.PhoneDetail
	 */
	@Test
	public void getDB2() throws Exception{
		FilterList list = new FilterList(FilterList.Operator.MUST_PASS_ALL);
		PrefixFilter filter1 = new PrefixFilter("182".getBytes());
		list.addFilter(filter1);
		Scan scan = new Scan();
		scan.setFilter(list);
		ResultScanner rss = table.getScanner(scan);
		for (Result rs : rss) {
			Phone.PhoneDetail phoneDetail = Phone.PhoneDetail.parseFrom(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "phoneDetail".getBytes())));
			System.out.println(phoneDetail.toString());
		}

	}


	/**
	 * 插入数据
	 * 有十个用户,每个用户每天产生100条记录,将100条记录放到一个集合进行存储
	 * 
	 * @throws Exception
	 */
	@Test
	public void insertDB4() throws Exception {
		List<Put> puts = new ArrayList<Put>();
		for (int i = 0; i < 100; i++) {
			String phoneNum = getPhoneNum("162");
			String dateStr = getDate("2020");
			String rowkey = phoneNum + "_" + (Long.MAX_VALUE - sdf.parse(dateStr).getTime());
			Phone.dayPhoneDetail.Builder dayPhone = Phone.dayPhoneDetail.newBuilder();
			for (int j = 0; j < 10; j++) {
				String dnum = getPhoneNum("158");
				String length = r.nextInt(99) + "";
				String type = r.nextInt(2) + "";
				Phone.PhoneDetail.Builder phoneDetail = Phone.PhoneDetail.newBuilder();
				phoneDetail.setDate(dateStr);
				phoneDetail.setDnum(dnum);
				phoneDetail.setLength(length);
				phoneDetail.setType(type);
				dayPhone.addDayPhoneDetail(phoneDetail);
			}
			Put put = new Put(rowkey.getBytes());
			put.addColumn("cf".getBytes(), "phoneDetail".getBytes(), dayPhone.build().toByteArray());
			puts.add(put);
		}
		table.put(puts);
	}

	/**
	 * 查询数据,不同的解析对象 Phone.dayPhoneDetail
	 */
	@Test
	public void getDB3() throws Exception{
		FilterList list = new FilterList(FilterList.Operator.MUST_PASS_ALL);
		PrefixFilter filter1 = new PrefixFilter("162".getBytes());
		list.addFilter(filter1);
		Scan scan = new Scan();
		scan.setFilter(list);
		ResultScanner rss = table.getScanner(scan);
		for (Result rs : rss) {
			Phone.dayPhoneDetail dayPhoneDetail = Phone.dayPhoneDetail.parseFrom(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "phoneDetail".getBytes())));
			System.out.println(dayPhoneDetail.toString());
		}

	}


	
	private String getDate(String year) {
		return year + String.format("%02d%02d%02d%02d%02d",
				new Object[] { r.nextInt(12) + 1, r.nextInt(31) + 1, r.nextInt(24), r.nextInt(60), r.nextInt(60) });
	}

	private String getDate2(String yearMonthDay) {
		return yearMonthDay
				+ String.format("%02d%02d%02d", new Object[] { r.nextInt(24), r.nextInt(60), r.nextInt(60) });
	}

	Random r = new Random();

	/**
	 * 生成随机的手机号码
	 * 
	 * @param string
	 * @return
	 */
	private String getPhoneNum(String string) {
		return string + String.format("%08d", r.nextInt(99999999));
	}


	@After
	public void destory() throws Exception {
		if (admin != null) {
			admin.close();
		}
	}
}

 

Hbase 优化

一张表 有多个 region

一个regionserver 上面有多个 region

一个 region 里面有 store(store数量和 列簇的数量相等)

一张完整的表被保存在多个Regionserver 上(region 是对表的横向切分,将 region 放到不同的 regionserver上面)

HRegion是HBase中分布式存储和负载均衡的最小单元。最小单元就表示不同的HRegion可以分布在不同的 HRegion server上。

A.表设计

1. 对Regin 进行分区

一种可以加快批量写入速度的方法是通过预先创建一些空的regions,这样当数据写入HBase时,会按照region分区情况,在集群内做数据的负载均衡。

2. rowkey尽可能短,减少保存的数据的大小

3.列簇的个数最多为  2 个

4.版本号 

设置数据的版本,一个rowkey的列簇中一个属性最多保存多少个版本的数据

setMaxVersions(1)

5.数据过期时间 time-to-live

数据的有效期,到期后自动删除

setTimeToLive(2 * 24 * 60 * 60)

6.compact 操作在系统闲暇的时候进行操作

在HBase中,数据在更新时首先写入WAL 日志(HLog)和内存(MemStore)中,MemStore中的数据是排序的,当MemStore累计到一定阈值时,就会创建一个新的MemStore,并且将老的MemStore添加到flush队列,由单独的线程flush到磁盘上,成为一个StoreFile。于此同时, 系统会在zookeeper中记录一个redo point,表示这个时刻之前的变更已经持久化了minor compact 将MEMStore数据持久化到磁盘 )

StoreFile是只读的,一旦创建后就不可以再修改。因此Hbase的更新其实是不断追加的操作。当一个Store中的StoreFile达到一定的阈值后,就会进行一次合并(major compact),将对同一个key的修改合并到一起,形成一个大的StoreFile,当StoreFile的大小达到一定阈值后,又会对 StoreFile进行分割(split),等分为两个StoreFile。

由于对表的更新是不断追加的,处理读请求时,需要访问Store中全部的StoreFile和MemStore,将它们按照row key进行合并,由于StoreFileMemStore都是经过排序的,并且StoreFile带有内存中索引,通常合并过程还是比较快的。

实际应用中,可以考虑必要时手动进行major compact,将同一个row key的修改进行合并形成一个大的StoreFile。同时,可以将StoreFile设置大些,减少split的发生。

  1. 关闭自动major compaction
  2. 手动编程major compaction
hbase.hregion.majorcompaction 这个参数来设置自动major 的周期,bai也可以设置为du0 取消掉自动 major

手动 对 phone 表进行 major compaction

major_compact 'phone'

minor compaction的运行机制要复杂一些,它由一下几个参数共同决定:
hbase.hstore.compaction.min :默认值为 3,表示至少需要三个满足条件的store file时,minor compaction才会启动
hbase.hstore.compaction.max 默认值为10,表示一次minor compaction中最多选取10个store file
hbase.hstore.compaction.min.size 表示文件大小小于该值的store file 一定会加入到minor compaction的store file中
hbase.hstore.compaction.max.size 表示文件大小大于该值的store file 一定会被minor compaction排除
hbase.hstore.compaction.ratio 将store file 按照文件年龄排序(older to younger),minor compaction总是从older store file开始选择

B.写操作

1. 多HTable并发写

需要解决数据覆盖的问题

2.Auto Flush

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

3.Write Buffer

HTable.setWriteBufferSize(writeBufferSize)  方法可以设置HTable客户端的写buffer大小,如果新设置的buffer小于当前写buffer中的数据时,buffer将会被flush到服务端。其中,writeBufferSize的单位是byte字节数,可以根据实际写入数据量的多少来设置该值。

4.WAL Flag(开启)

在HBae中,客户端向集群中的RegionServer提交数据时(Put/Delete操作),首先会先写WAL(Write Ahead Log)日志(即HLog,一个RegionServer上的所有Region共享一个HLog),只有当WAL日志写成功后,再接着写MemStore,然后客户端被通知提交数据成功;如果写WAL日志失败,客户端则被通知提交失败。这样做的好处是可以做到RegionServer宕机后的数据恢复

5.批量写

HTable.put(List<Put>)方法可以将指定的row key列表,批量写入多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于对数据实时性要求高,网络传输RTT高的情景下可能带来明显的性能提升。

 

C.读操作

1.多HTable并发读

2.Scanner Caching

hbase.client.scanner.caching配置项可以设置HBase scanner一次从服务端抓取的数据条数,默认情况下一次一条。通过将其设置成一个合理的值,可以减少scan过程中next()的时间开销,代价是scanner需要通过客户端的内存来维持这些被cache的行记录

有三个地方可以进行配置:

1)在HBase的conf配置文件中进行配置;

2)通过调用HTable.setScannerCaching(int scannerCaching)进行配置;

3)通过调用Scan.setCaching(int caching)进行配置。

三者的优先级越来越高。

3.Scan Attribute Selection

scan时指定需要的Column Family,可以减少网络传输数据量,否则默认scan操作会返回整行所有Column Family的数据。

4.Close ResultScanner

通过scan取完数据后,记得要关闭ResultScanner,否则RegionServer可能会出现问题(对应的Server资源无法释放)。

5.批量读

HTable.get(List<Get>) 方法可以根据一个指定的row key列表,批量获取多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于对数据实时性要求高而且网络传输RTT高的情景下可能带来明显的性能提升。

6.缓存查询结果缓存查询结果

在应用程序中将查询结果缓存起来

7. Blockcache

HBase上Regionserver的内存分为两个部分,一部分作为Memstore,主要用来写;另外一部分作为BlockCache,主要用于读。

写请求会先写入Memstore,Regionserver会给每个region提供一个Memstore,当Memstore满64MB以后,会启动 flush刷新到磁盘。当Memstore的总大小超过限制时(heapsize * hbase.regionserver.global.memstore.upperLimit * 0.9),会强行启动flush进程,从最大的Memstore开始flush直到低于限制。

读请求先到Memstore中查数据,查不到就到BlockCache中查,再查不到就会到磁盘上读,并把读的结果放入BlockCache。由于BlockCache采用的是LRU策略,因此BlockCache达到上限(heapsize * hfile.block.cache.size * 0.85)后,会启动淘汰机制,淘汰掉最老的一批数据。

一个Regionserver上有一个BlockCache和N个Memstore,它们的大小之和不能大于等于heapsize * 0.8,否则HBase不能启动。默认BlockCache为0.2,而Memstore为0.4。对于注重读响应时间的系统,可以将 BlockCache设大些,比如设置BlockCache=0.4,Memstore=0.39,以加大缓存的命中率。

 

D.HTablePool解决HTable存在的线程不安全问题

1.   HTablePool可以自动创建HTable对象,而且对客户端来说使用上是完全透明的,可以避免多线程间数据并发修改问题。

2.   HTablePool中的HTable对象之间是公用Configuration连接的,能够可以减少网络开销。

 

 

问题

1.分组 development 不包含任何可安装软件包。

出现这种问题应该是已经安装过软件组了。

先卸载软件组里面的某个软件

yum remove gcc

 再次安装软件组

 yum groups install Development Tools 

安装完之后,再次运行出现了 类似的提示信息,因为在linux 系统安装的时候是有让选择都安装什么样的依赖,所以 软件组 的安装是不会出现什么莫名其妙的异常,让去进行处理的。 

2.Please use 'syntax = "proto2";' or 'syntax = "proto3";' to specify a syntax version. (Defaulted to proto2 syntax. 

重新编辑,xxx.proto 文件指定  syntax = "proto2"; 或者 syntax = "proto3"; 

3.InvalidProtocolBufferException: Message missing required fields: length, type, date

在使用 protocol 进行数据查询的时候,由于数据类型装换错误出现了异常

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值