HBase CURD之Put

HBase数据插入使用Put对象,Put对象在进行数据插入时,首先会向HBase集群发送一个RPC请求,得到相应之后将Put类中的数据通过序列化的方式传给HBase集群,集群节点接收到数据之后进行添加功能。

单行插入

单行插入即每次只插入一行数据,下面先看一个插入一条数据的代码:

@Test
public void testPut() throws IOException {
    Connection conn = ConnectionFactory.createConnection();
    HTable table = (HTable) conn.getTable(TableName.valueOf("ns1:t1"));
    Put put = new Put(Bytes.toBytes("row1"));
    put.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("id"), Bytes.toBytes(1));
    put.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("name"), Bytes.toBytes("tom"));
    put.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("age"), Bytes.toBytes(15));
    table.put(put);
    table.close();
    conn.close();
}

从上面的代码可以看出插入一条数据是使用HTable对象的put方法添加,put方法中的参数是Put对象。好下面就来讲讲Put对象。

Put的构造函数

public Put(byte [] row)                                               // 只指定rowKey
public Put(byte [] rowArray, int rowOffset, int rowLength)            // 从一个字节数组中指定rowKey
public Put(byte [] rowArray, int rowOffset, int rowLength, long ts)   // 从一个字节数组中指定rowKey,并且设置时间戳
public Put(byte[] row, long ts)                                       // 指定rowKey和时间戳
public Put(ByteBuffer row)
public Put(ByteBuffer row, long ts)
public Put(Put putToCopy)                                             // 从其他put对象拷贝

创建Put对象时用户必须指定rowKey,在HBase中每行数据都有一个唯一的行键(rowKey)作为标识,跟HBase的大多数数据类型一样,它是一个字节数组。用户可以按照自己的需求指定每行的行键。通常情况下,rowKey的含义与真实场景相关,例如他的含义可以是一个用户名或者订单号,它的内容可以是简单的数据,也可以是比较复杂的UUID(全局统一标识符)等。

HBase为用户提供了一个包含很多静态方法的辅助类,这个类可以将许多java数据类型转换为字节数组。

Bytes类中的静态方法

public static byte[] toBytes(ByteBuffer bb)
public static byte[] toBytes(String s)
public static byte[] toBytes(boolean b)
public static byte[] toBytes(long val)
public static byte[] toBytes(float f)
public static byte[] toBytes(int val)

创建Put对象之后,就可以向该对象中添加数据了,添加数据的方法如下:

public Put add(Cell kv)
public Put addColumn(byte [] family, byte [] qualifier, byte [] value)
public Put addColumn(byte [] family, byte [] qualifier, long ts, byte [] value)
public Put addColumn(byte[] family, ByteBuffer qualifier, long ts, ByteBuffer value)

每一次调用add()或者addColumn()方法都可以添加一行数据,如果再加上一个时间戳选项,就能成为一个数据单元格。注意,当不指定时间戳时,Put对象会使用来自构造函数中的可选时间戳,如果用户在构造Put对象的时候也没有指定时间戳,则时间戳将会由regionServer设定。

系统为一些用户提供了Cell实例的变种,这里说的高级用户是指知道怎样检索或创建这个内部类的用户。Cell实例代表了一个唯一的数据单元格,类似于一个协调系统,该系统使用行键、列族、列限定符、时间戳指向一个单元格的值,像一个三维立方体系统。

获取Put对象内部的Cell对象需要调用以下方法:

public List<Cell> get(byte[] family, byte[] qualifier)
public NavigableMap<byte [], List<Cell>> getFamilyCellMap()

以上两个方法可以查询用户之间添加的内容,同时将特定单元格的信息转换成Cell对象。用户可以选择获取整个列族的全部数据单元,一个列族中的特定列或者是全部数据。后面的getFamilyCellMap()方法可以遍历Put对象中的每个可用的Cell对象。

用户可以采用以下方法检测是否存在特定的单元格,而不需遍历整个集合:

boolean has(byte[] family,byte[] qualifier)
boolean has(byte[] family,byte[] qualifier,long ts)
boolean has(byte[] family,byte[] qualifier,byte[] value)
boolean has(byte[] family,byte[] qualifier,long ts,byte[] value)

Put类的其它方法

方法 描述
getRow() 返回创建Put实例时所指定的行键
getRowLock() 返回当前Put实例的行RowLock实例
getLockId() 返回使用rowlock参数传递给构造函数的可选的锁ID,当未被指定时返回-1L
setWriteToWAL() 允许关闭默认启用的服务端预写日志(WAL)功能
getWriteToWAL() 返回代表是否启用了WAL功能
getTimeStamp() 返回相应Put实例的时间戳,该值可在构造函数中由ts参数传入,当未被设定时返回Long.MAX_VALUE
heapSize() 计算当前Put实例所需的堆大小,既包含其中的数据,也包含内部数据结构所需的空间
isEmpty() 检查FamilyMap是否含有任何KeyValue实例
numFamilies() 查询FamilyMap的大小,即所有的KeyValue实例中列族的个数
size() 返回本次Put会添加的KeyValue的实例

客户端的写缓冲区

每一个put操作实际上都是一个RPC操作,它将客户端数据传送到服务器然后返回。这只适合小数据量的操作,如果有个应用程序需要每秒存储上千行数据到HBase表中,这样处理就不太合适了。

Tip

减少独立RPC调用的关键是限制往返时间,往返时间就是客户端发送一个请求到服务器,然后服务器通过网络进行相应的时间。这个时间不包含 数据实际传输的时间,它其实就是通过线路传送网络包的开销。一般情况下,在LAN网络中大约要花1ms的时间,这意味着在一秒的时间内只能完成1000次RPC往返相应。
另一个重要的因素就是消息大小。如果通过网络发送的请求内容较大,那么需要请求返回的次数相应较少,这是因为时间主要花费在数据传递上。不过如果传送的数据量很小,比如一个计数器递增操作,那么用户把多次修改的数据批量提交给服务器并减少请求次数,性能会有相应提升。

HBase的API配备了一个客户端的写缓冲区,缓冲区负责收集put操作,然后调用RPC操作一次性将put送往服务器。全局交换机控制着该缓冲区是否在使用,以下是其方法:

void setAutoFlushTo(boolean autoFlush)
boolean isAutoFlush()

默认情况下,客户端缓冲区是禁用的。可以通过将自动刷新设置为false来激活缓冲区,调用如下:
table.setAutoFlushTo(false)

激活客户端缓冲区之后,将数据存储到HBase中。此时的操作不会产生RPC调用,因为存储Put实例保存在客户端进程中的内存中。当需要强制把数据写到服务器时,可以调用table.flushCommits()

flushCommits()方法将所有的修改传送给远程服务器。被缓冲的Put实例可以跨多行。客户端能够批量处理这些更新,并把他们传送到对应regionServer。和调用单行put()方法一样,用户不需要担心数据分配到了哪里,因为对于用户来说,HBase客户端对这个方法的处理是透明的,下图展示了在客户端请求传送到服务器之前是怎样按regionServer排序分组,并通过每个regionServer的RPC请求将数据传送到服务器的。

客户端put操作按所属region服务器排序和分组

缓冲区仅在两种情况下会刷新

  • 显示刷新
    • 用户调用flushCommit()方法,把数据发送到服务器做永久存储。
  • 隐式刷新
    • 隐式刷新会在用户调用put或setWriteBufferSize()方法时触发。这两个方法都会将目前占用的缓冲区大小与用户配置的大小做比较,如果超出限制则会调用flushCommits()方法。如果缓冲区被禁用,可以设置setAutoFlushTo(true),这样用户每次调用put方法是都会触发刷写。

批量插入示例代码

/**
 * 批量添加100000条数据,每2000条一刷写
 */
@Test
public void testBatchPut() throws IOException {
    Connection conn = ConnectionFactory.createConnection();
    HTable table = (HTable) conn.getTable(TableName.valueOf("ns1:t1"));

    table.setAutoFlushTo(false);

    for (int i = 0; i < 100000; i++) {
        Put put = new Put(Bytes.toBytes("row" + i));
        put.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("id"), Bytes.toBytes(i));
        put.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("name"), Bytes.toBytes("tom" + i));
        put.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("age"), Bytes.toBytes(i % 100));

        table.put(put);
        if (i % 2000 == 0){
            table.flushCommits();
        }
    }
    table.flushCommits();
    table.close();
    conn.close();
}

Put列表

客户端可以将一个Put实例的集合进行插入操作,实现代码如下:

/**
 * 批量插入1000条数据
 */
@Test
public void testBatchPut2() throws IOException {
    Connection conn = ConnectionFactory.createConnection();
    HTable table = (HTable) conn.getTable(TableName.valueOf("ns1:t1"));

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

    for (int i = 0; i < 1000; i++) {
        Put put = new Put(Bytes.toBytes("r" + i));
        put.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("id"), Bytes.toBytes(i));
        put.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("name"), Bytes.toBytes("tom" + i));
        put.addColumn(Bytes.toBytes("f1"), Bytes.toBytes("age"), Bytes.toBytes(i % 100));

        puts.add(put);

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

原子性操作compare-and-set

有一个特别的普通调用,其能保证自身操作的原子性,检查和写(check and put)方法如下:

public boolean checkAndPut(final byte [] row,
      final byte [] family, final byte [] qualifier, final byte [] value,
      final Put put)

public boolean checkAndPut(final byte [] row, final byte [] family,
      final byte [] qualifier, final CompareOp compareOp, final byte [] value,
      final Put put)

测试代码如下

Put put1 = new Put(Bytes.toBytes("row1"));
put1.add(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"),
         Bytes.toBytes("val1"));
boolean res1 = table.checkAndPut(Bytes.toBytes("row1"),
                                 Bytes.toBytes("qual1"), Bytes.toBytes("val1"), null, put1);
System.out.println("Put applied: " + res1);
table.put(put1);
boolean res2 = table.checkAndPut(Bytes.toBytes("row1"),
                                 Bytes.toBytes("qual1"), Bytes.toBytes("val1"), null, put1);
System.out.println("Put applied: " + res2);
Put put2 = new Put(Bytes.toBytes("row2"));
put2.add(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"),
         Bytes.toBytes("val2"));
boolean res3 = table.checkAndPut(Bytes.toBytes("row1"),
                                 Bytes.toBytes("qual1"), Bytes.toBytes("val1"), null, put2);
System.out.println("Put applied: " + res3);
table.put(put2);
Put put3 = new Put(Bytes.toBytes("row3"));
put3.add(Bytes.toBytes("colfam1"), Bytes.toBytes("qual3"),
         Bytes.toBytes("val3"));
boolean res4 = table.checkAndPut(Bytes.toBytes("row1"),
                                 Bytes.toBytes("qual1"), Bytes.toBytes("val1"), null, put3);
System.out.println("Put applied: " + res4);

原文链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容