hbase:
1.hbase是数据库
特点:高可靠性,高性能,面向列,可伸缩,实时读写
数据量:十亿级别的行
百万级别的列
速度快的原因:1充分利用内存 2会用了LSM树 3 缓存机制 4文件是顺序读
2数据模型
rowkey
rowkey相当于mysql中的主键,唯一标识一行记录
rowkey是字典序
rowkey的长度最长是64kb,但是一般推荐10-100字节
colunm family
一组列的集合
列族必须作为表的schema定义给出
列族是权限,存储的最小单元
qulifier
列
可以动态的,随机的插入
表定义之后没有限制列,随着值的插入也把列插入
列必须归属于某一个列族
timestamp
时间戳,64位整数,精度是毫秒
器版本号的作用,一个cell中可以存在多版本的数据
时间戳可以自己定义,但是一般不推荐
cell
存储数据的最小单元(逻辑概念)
存储的是KV格式的数据 K:rowkey+column family +qulifier+timestamp
V:value
hbase的cell存储数据的时候没有类型的区分,存放的都是字节数组
3.架构
hbase是主从架构
角色:
client:操作hbase的接口(命令行和java api)并维护客户端缓存
zookeeper:保证任何时刻集群中有且仅有一台active的master
存储所有region的寻址入口--所有region元数据存储在哪一台regionserver
监控regionserver的上线和下线信息,并实时通知master
存储相关的表的schema数据
master:分配region
保证整个集群中的所有regionserver负载均衡
当发现某一台regionserver宕机之后,重新分配上面的region
当region变大进行裂变的时候,master去分配region到哪一台regionserver
regionserver:负责接收客户端的读写请求,处理对于region的IO
当某一个region变大之后,负责等分为两个region
region:相当于表的概念。一张表至少对应一个region。
当表的数据过大,region会发生裂变(等分)
store:相当于列族
store里有角色memstore和storefile
memstore位于内场,每一个store有一个memstore
storefile磁盘存储空间,将数据持久化的存储位置
每一个region有一个或者多个storefile
storefile可以进行合并操作
store存储结构:使用了LSM的数据模型
WAL:write ahead log
防止数据丢失
先写内存,再向hdfs上溢写,但是是异步的方式
读写流程
读流程
1.客户端向zk中发送请求
2.从zk中拿到metadata的存储节点
3去存储metadata的节点获取对应region的所在位置
4访问对应的region获取数据
5先去memstore中查询数据,如果有结果,直接返回
6如果没有查询到结果,去blockcache查找数据,如果找到,直接返回
7如果没有找到,去storefile中查找数据,并将查询到的结果缓存回blockcache中,方便下一次查询
8将结果返回给客户端
注意:blockcache是缓存,有大小限制,会有淘汰机制,默认将最早的数据淘汰
写流程
1.client向zk发送请求
2.从zk中拿到metadata的存储节点
3去存储metadata的节点获取对应region的所在位置
4访问对应的region写数据
5先首先会向wal中写数据,写成功后才会存储到memstore
6.当memstore中的数据量达到阈值之后,进行溢写,溢写成storefile
7.store file是一个个的小文件,会进行合并(minor(一般自动触发),major(一般手动触发))
8.storefile是对hfile的封装。hfile是实际存储在hdfs上的数据文件
JAVA API
hbase admin:
管理表 :createtable disabletable deletetable
HTable:管理数据:put get scan delete
对于通话记录的表的设计
通话记录表所要记录的数据有:手机号,通话时长,对方手机号,日期,类型(主叫,被叫)
按时间戳倒序,所以在hbase中应该如何设计?
设计如下phone_(long.maxValue-timestamp)
cf:length=,cf:data=,cf:dnum=,cf:type=
设计如下
public void insert() throws Exception {
List puts = new ArrayList();
for (int i = 0; i < 10; i++) {
String phoneNumber = getPhone("158");//158开头的十一位整数
for (int j = 0; j < 1000; j++) {
// 属性
String dnum = getPhone("177");
String length = String.valueOf(r.nextInt(99));
String type = String.valueOf(r.nextInt(2));
String date = getDate("2018");
// rowkey设计
String rowkey = phoneNumber + "_" + (Long.MAX_VALUE - sdf.parse(date).getTime());
Put put = new Put(rowkey.getBytes());
put.add("cf".getBytes(), "dnum".getBytes(), dnum.getBytes());
put.add("cf".getBytes(), "length".getBytes(), length.getBytes());
put.add("cf".getBytes(), "type".getBytes(), type.getBytes());
put.add("cf".getBytes(), "date".getBytes(), date.getBytes());
puts.add(put);
}
}
table.put(puts);
}
对于以下
查询某一个用户3月份的所有通话记录
/**
* 查询某一个用户3月份的所有通话记录 条件: 1、某一个用户 2、时间
*
* @throws Exception
*/
@Test
public void scan() throws Exception {
String phoneNumber = "15895223166";
String startRow = phoneNumber + "_" + (Long.MAX_VALUE - sdf.parse("20180401000000").getTime());//取三月份通话记录,所以04 四月是start(倒序)
String stopRow = phoneNumber + "_" + (Long.MAX_VALUE - sdf.parse("20180301000000").getTime());
Scan scan = new Scan();
scan.setStartRow(startRow.getBytes());
scan.setStopRow(stopRow.getBytes());
ResultScanner scanner = table.getScanner(scan);
for (Result result : scanner) {
System.out.print(Bytes
.toString(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "dnum".getBytes()))));
System.out.print("--" + Bytes
.toString(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "type".getBytes()))));
System.out.print("--" + Bytes
.toString(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "date".getBytes()))));
System.out.println("--" + Bytes
.toString(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "length".getBytes()))));
}
}
查询某一个用户。所有的主叫电话 .设置过滤器
/**
* 查询某一个用户。所有的主叫电话 条件: 1、电话号码 2、type=0
*
* @throws Exception
*/
@Test
public void scan2() throws Exception {
FilterList filters = new FilterList(FilterList.Operator.MUST_PASS_ALL);
SingleColumnValueFilter filter1 = new SingleColumnValueFilter("cf".getBytes(), "type".getBytes(),
CompareOp.EQUAL, "0".getBytes());
PrefixFilter filter2 = new PrefixFilter("15895223166".getBytes());
filters.addFilter(filter1);
filters.addFilter(filter2);
Scan scan = new Scan();
scan.setFilter(filters);
ResultScanner scanner = table.getScanner(scan);
for (Result result : scanner) {
System.out.print(Bytes
.toString(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "dnum".getBytes()))));
System.out.print("--" + Bytes
.toString(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "type".getBytes()))));
System.out.print("--" + Bytes
.toString(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "date".getBytes()))));
System.out.println("--" + Bytes
.toString(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "length".getBytes()))));
}
scanner.close();
}
代码:
public class PhoneCase {
// 表的管理类
HBaseAdmin admin = null;
// 数据的管理类
HTable table = null;
// 表名
String tm = "phone";
/**
* 完成初始化功能
*
* @throws Exception
*/
@Before
public void init() throws Exception {
Configuration conf = new Configuration();
conf.set("hbase.zookeeper.quorum", "node1,node2,node3");
admin = new HBaseAdmin(conf);
table = new HTable(conf, tm.getBytes());
}
/**
* 创建表
*
* @throws Exception
*/
@Test
public void createTable() throws Exception {
// 表的描述类
HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(tm));
// 列族的描述类
HColumnDescriptor family = new HColumnDescriptor("cf".getBytes());
desc.addFamily(family);
if (admin.tableExists(tm)) {
admin.disableTable(tm);
admin.deleteTable(tm);
}
admin.createTable(desc);
}
/**
* 10个用户,每个用户每年产生1000条通话记录
*
* dnum:对方手机号 type:类型:0主叫,1被叫 length:长度 date:时间
*
* @throws Exception
*
*/
//插入数据
@Test
public void insert() throws Exception {
List puts = new ArrayList();
for (int i = 0; i < 10; i++) {
String phoneNumber = getPhone("158");//158开头的十一位整数
for (int j = 0; j < 1000; j++) {
// 属性
String dnum = getPhone("177");
String length = String.valueOf(r.nextInt(99));
String type = String.valueOf(r.nextInt(2));
String date = getDate("2018");
// rowkey设计
String rowkey = phoneNumber + "_" + (Long.MAX_VALUE - sdf.parse(date).getTime());
Put put = new Put(rowkey.getBytes());
put.add("cf".getBytes(), "dnum".getBytes(), dnum.getBytes());
put.add("cf".getBytes(), "length".getBytes(), length.getBytes());
put.add("cf".getBytes(), "type".getBytes(), type.getBytes());
put.add("cf".getBytes(), "date".getBytes(), date.getBytes());
puts.add(put);
}
}
table.put(puts);
}
/**
* 查询某一个用户3月份的所有通话记录 条件: 1、某一个用户 2、时间
*
* @throws Exception
*/
@Test
public void scan() throws Exception {
String phoneNumber = "15895223166";
String startRow = phoneNumber + "_" + (Long.MAX_VALUE - sdf.parse("20180401000000").getTime());
String stopRow = phoneNumber + "_" + (Long.MAX_VALUE - sdf.parse("20180301000000").getTime());
Scan scan = new Scan();
scan.setStartRow(startRow.getBytes());
scan.setStopRow(stopRow.getBytes());
ResultScanner scanner = table.getScanner(scan);
for (Result result : scanner) {
System.out.print(Bytes
.toString(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "dnum".getBytes()))));
System.out.print("--" + Bytes
.toString(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "type".getBytes()))));
System.out.print("--" + Bytes
.toString(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "date".getBytes()))));
System.out.println("--" + Bytes
.toString(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "length".getBytes()))));
}
}
/**
* 查询某一个用户。所有的主叫电话 条件: 1、电话号码 2、type=0
*
* @throws Exception
*/
@Test
public void scan2() throws Exception {
FilterList filters = new FilterList(FilterList.Operator.MUST_PASS_ALL);
SingleColumnValueFilter filter1 = new SingleColumnValueFilter("cf".getBytes(), "type".getBytes(),
CompareOp.EQUAL, "0".getBytes());
PrefixFilter filter2 = new PrefixFilter("15895223166".getBytes());
filters.addFilter(filter1);
filters.addFilter(filter2);
Scan scan = new Scan();
scan.setFilter(filters);
ResultScanner scanner = table.getScanner(scan);
for (Result result : scanner) {
System.out.print(Bytes
.toString(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "dnum".getBytes()))));
System.out.print("--" + Bytes
.toString(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "type".getBytes()))));
System.out.print("--" + Bytes
.toString(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "date".getBytes()))));
System.out.println("--" + Bytes
.toString(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "length".getBytes()))));
}
scanner.close();
}
/**
* 日期
* @param string
* @return
*/
private String getDate(String string) {
return string + String.format("%02d%02d%02d%02d%02d", r.nextInt(12) + 1, r.nextInt(31), r.nextInt(24),
r.nextInt(60), r.nextInt(60));
}
Random r = new Random();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddhhmmss");
//电话
private String getPhone(String phonePrefix) {
return phonePrefix + String.format("%08d", r.nextInt(99999999));
}
/**
* 10个用户,每个用户1000条,每一条记录当作一个对象进行存储
* @throws Exception
*/
@Test
public void insert2() throws Exception{
List puts = new ArrayList();
for(int i = 0;i<10;i++){
String phoneNumber = getPhone("158");
for(int j = 0;j<1000;j++){
String dnum = getPhone("177");
String length =String.valueOf(r.nextInt(99));
String type = String.valueOf(r.nextInt(2));
String date = getDate("2018");
//保存属性到对象中
Phone.PhoneDetail.Builder phoneDetail = Phone.PhoneDetail.newBuilder();
phoneDetail.setDate(date);
phoneDetail.setLength(length);
phoneDetail.setType(type);
phoneDetail.setDnum(dnum);
//rowkey
String rowkey = phoneNumber+"_"+(Long.MAX_VALUE-sdf.parse(date).getTime());
Put put = new Put(rowkey.getBytes());
put.add("cf".getBytes(), "phoneDetail".getBytes(), phoneDetail.build().toByteArray());
puts.add(put);
}
}
table.put(puts);
}
@Test
public void get() throws Exception{
Get get = new Get("15866626435_9223370522183722807".getBytes());
Result result = table.get(get);
PhoneDetail phoneDetail = Phone.PhoneDetail.parseFrom(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "phoneDetail".getBytes())));
System.out.println(phoneDetail);
}
/**
* 10个用户,每天产生1000条记录,每一天的所有数据放到一个rowkey中
* @throws Exception
*/
@Test
public void insert3() throws Exception{
List puts = new ArrayList();
for(int i = 0;i<10;i++){
String phoneNumber = getPhone("133");
String rowkey = phoneNumber+"_"+(Long.MAX_VALUE-sdf.parse("20181225000000").getTime());
Phone.DayOfPhone.Builder dayOfPhone = Phone.DayOfPhone.newBuilder();
for(int j=0;j<1000;j++){
String dnum = getPhone("177");
String length =String.valueOf(r.nextInt(99));
String type = String.valueOf(r.nextInt(2));
String date = getDate2("20181225");
Phone.PhoneDetail.Builder phoneDetail = Phone.PhoneDetail.newBuilder();
phoneDetail.setDate(date);
phoneDetail.setLength(length);
phoneDetail.setType(type);
phoneDetail.setDnum(dnum);
dayOfPhone.addDayPhone(phoneDetail);
}
Put put = new Put(rowkey.getBytes());
put.add("cf".getBytes(), "day".getBytes(), dayOfPhone.build().toByteArray());
puts.add(put);
}
table.put(puts);
}
@Test
public void get2() throws Exception{
Get get = new Get("13398049199_9223370491187575807".getBytes());
Result result = table.get(get);
DayOfPhone parseFrom = Phone.DayOfPhone.parseFrom(CellUtil.cloneValue(result.getColumnLatestCell("cf".getBytes(), "day".getBytes())));
int count = 0;
for (PhoneDetail pd : parseFrom.getDayPhoneList()) {
System.out.println(pd);
count++;
}
System.out.println(count);
}
private String getDate2(String string) {
return string+String.format("%02d%02d%02d", r.nextInt(24),r.nextInt(60),r.nextInt(60));
}
@After
public void destory() throws Exception {
if (admin != null) {
admin.close();
}
}
}
第二个表的设计
角色表的设计
人员有多个角色 角色优先级
角色有多个人员
人员 删除添加角色
角色 可以添加删除人员
人员 角色 删除添加
001小红 班长100,学委200
002小白 学委,劳委300
003小黑 劳委,体委400
一张表:
人员角色表
rowkey: | cf1:(基本信息) | cf2:(角色列表) | cf3:(人员列表) |
---|---|---|---|
pid | cf1:pname= ,cf1:page= | cf2:rid=?,cf2:rid2=? | |
rid | cf1:rname=?,cf1:rdesc=?(r描述) | cf3:pid=?,cf3:pid2=? |
一张表可能不是很方便,但是达到了需求
两张表
psn
rowkey:pid | cf1:(人员信息) | cf2:(角色列表) |
---|---|---|
001 | cf1:name=小红 | cf2:100=班长,cf2:200=学委(如果要满足优先级,可以设为cf2:100=10,cf2:200=9) |
002 | cf1:name=小白 | cf2:200=。。。,cf2:300=。。。 |
003 | cf1:name=小黑 | cf2:300=。。。,cf2:400=。。。 |
004 | cf1:name=小绿 |
role
rowkey: rid | cf1:(角色信息) | cf2:(人员列表) |
---|---|---|
100 | cf1:name=班长 | cf2:001=小红 |
200 | cf1:name=学委 | cf2:001=小红,cf2:002=小白 |
300 | cf1:name=劳委 | cf2:001=小黑,cf2:002=小白 |
400 | cf1:name=体委 | cf2:001=小黑, |
hbase存在observer,功能与mysql中触发器类似