AIX重启

切记不可使用reboot命令,这个命令在单用户模式下是重启服务器,而多用户模式下,该命令只是关机,而且可能会导致文件系统的损坏
正确的重启方式是shutdown -Fr

查看HACMP进程是否正常

#lssrc -g cluster 
subsystem    group    PID    status
clstrmgrES     cluster   22454  active
clinfoES    cluster   15874  active

HACMP服务的启动和停止

  • 启动: 要两个节点同时启动,停止时只停止1个节点

    #smitty clstart 
    Start Cluster Services 
    
    [输入字段]                           
    * Start now, on system restart or both now +
    Start Cluster Services on these nodes [EASNODE1 EASNODE2] +
    BROADCAST message at startup? true +
    Startup Cluster Lock Services? false +
    Startup Cluster Information Daemon? true +   
    Reacquire resources after forced down ? false +
    
  • 停止: 停止的时只停止1个节点

    #smitty clstop 
    Stop Cluster Services
    [输入字段]
    * Stop now, on system restart or both now +
    Stop Cluster Services on these nodes [EASNODE1] +
    BROADCAST cluster shutdown? true +
    * Shutdown mode graceful[MOVE GROUP RESOURE]+  
    

#NoSQL学习模式

  • 基本概念
  • 安装
  • 数据类型与数据模型
  • 与应用程序对接
  • Shell操作命令
  • 主从复制
  • 分片
  • 管理维护
  • 应用案例

ObjectID官方指南

ObjectID是类似与“507f1f77bcf86cd799439011”的24位字符串,占用12字节,生成规则如下:

ObjectId is a 12-byte BSON type, constructed using:
- 4-byte value representing the seconds since the Unix epoch,
- 3-byte machine identifier,
- 2-byte process id, and
- 3-byte counter, starting with a random value.

自定义生成ObjectID

参考MongoDB的ObjectID生成规则,可自定义生成轻量级的惟一码MyUUID,只需占用8字节
其中MyUUID的生成规则如下:

| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---------------------------------
|     time      |mid|   inc     |

第0~3字节存放时间,第4字节存放MachineID,第5~7字节存放自增长ID

MyUUID源码

在一般PC机上,1秒可生成十万不重复的MyUUID,在服务器上效率肯定更高,应该能够满足一般应用要求

import java.util.Date;

class MyUUID
{
    private static int _machineID=0;
    private static int _incID=0;

    private static int getMachineID() {
        //复杂些的可以通过主机主板序列号、IP、硬盘序列号等生成机器码,简单起见默认为1
        _machineID+=1;
        return _machineID%127;
    }

    private static int getInc()
    {
        //一般使用Memcached统一维护一个全局自增ID,简单起见默认为1
        _incID+=1;
        return _incID;
    }

    public static long ObjectID() {  
        long v_time = (int) (System.currentTimeMillis() / 1000);
        int v_machine = getMachineID();
        int v_inc = getInc();

        long myUUID=0;
        myUUID=myUUID | v_inc;
        myUUID=myUUID | (v_machine<<24);
        myUUID=myUUID | (v_time<<32);

        return myUUID;
    }

    public static void reverseObjectID(long myUUID) 
    {
        //反向解析myUUID
        int v_inc =(int) myUUID & 0xFFFFFF;//取出最右边3字节(即24bit)的值,每个16进制数存储4bit
        int v_machine=(int) (myUUID >>> 24) & 0xFF;//无符号右移24位,然后取最右边1字节的值
        long v_time=(myUUID >>>32) & 0xFFFFFFFF;//无符号右移32位,然后取四字节的秒数

        System.out.println("v_time="+Long.toHexString(v_time));
        System.out.println("时间为:"+new java.util.Date(v_time*1000));
        System.out.println("v_machine="+v_machine);
        System.out.println("inc="+v_inc);
    }

    public static void main(String[] args) 
    {
        long objectid;
        long id1=0,id2=0;

        //生成100000个MyUUID
        for (int i=0; i<100000; i++)
        {
            objectid=ObjectID();
            System.out.println("MyUUID="+Long.toHexString(objectid));

            if (i==0)
                id1=objectid;
            else if (i==99999)
                id2=objectid;
            else;
        }

        System.out.println("反向解析MyUUID...................");
        reverseObjectID(id1);
        reverseObjectID(id2);
    }
}

本文转载自QQ天堂DB2的备份(backup)和恢复(RESTORE)数据库方法

一、备份(backup)数据库

1、离线全备份

1)、首先确保没有用户使用DB2:
db2 list applications for db sample
2)、停掉数据库并重新启动,以便断掉所有连接:
db2stop force
db2start
3)、执行备份命令:(使用TSM作为备份的介质)
db2 backup db sample use tsm
备份成功,将会返回一个时间戳。
4)、检查备份成功:
db2 list history backup all for sample 可以看到多了这个备份的纪录。
db2adutl query 命令也可以看到返回值。
5)、备注:
首先对主节点(catalog表空间在的节点)执行备份命令,再对另外的节点也做这个操作。

2、 在线备份:

1)、首先打开一下支持在线备份的数据库配置参数:
db2 update db cfg for sample using userexit on 启用用户出口
db2 update db cfg for sample using logretain on 启用归档日志
db2 update db cfg for sample using trackmod on 启用增量备份功能
(需要各个Node都分别做设置)
开启这些参数后,数据库处于backup pending状态,要求做数据库的离线全备份。做一下离线全备份,参考上面的命令。
2)、在线备份命令如下:
db2 backup db sample online use tsm
备份成功,返回一个时间戳。
3)、同样可以用db2adutl 和db2 list history察看备份纪录。
4)、备注:
同样,对每个节点都做这个操作。

3、 在线增量备份

1)、在开启了必须的三个参数的情况下,做增量备份:
db2 backup db sample online incremental use tsm
备份成功,返回一个时间戳。
2)、同样可以用db2adutl 和db2 list history察看备份纪录。
3)、还有一种delta的备份:
db2 backup db sample online incremental delta use tsm
这两种备份的区别,类似Oracle Exports的Incremental和Cumulative方式,db2的incremental对应oracle的cumulative方式,而db2的delta方式则对应oracle的incremental方式。
4)、备注:
同样,对每个节点都做这个操作。

二、恢复(RESTORE)数据库

1、新建数据库

在一般管理工具, 创建数据库(别名要和原来的数据库别名一致)。

2、恢复数据库

1):断开连接:将网络断开,切断所有的客户连接,如果不行,先重启。(如果有客户端连接到DB2服务器,恢复不行的,包括控制中心)

2):恢复离线备份:
   设:
   你的备份那个DB2文件在:C:\Temp
   你的数据库名称:DBName
   那么对应DB2的备份/恢复文件的格式是这样的:
   C:\Temp\DBName.0\DB2CTLSV\NODE0000\CATN0000\20090706\HHMMSS.001

备份时你只是指定了一个C:\Temp目录,后面那么目录为DB2备份时生成。
   然后是它的文件名,一般格式是:HHMMSS.001,即:小时分钟秒数.001,也就是说后面那个20020828是备份日期,文件名是备份时间。
  
   好,清楚后,你就可以写SQL命令进行恢复了
   运行: Start->Program->IBM DB2->Command Center
   然后:不要登陆,直接输入下列命令:
  
以下为引用的内容:
RESTORE DATABASE NewDBName FROM C:\Temp\ TAKEN AT
20020919094932 TO D: INTO OldDBName WITH 2 BUFFERS BUFFER 1024
WITHOUT PROMPTING  
  你要改
  NewDBName ==》你的新建数据库名称
  C:\TEMP\ ==> 你备份DB2的目录,像上面所说
  20020919094932 ==> 一看你DB2的目录和文件组合成这东东
  D: ==> 新建的数据库放在哪个盘上
  OldDBName ==> 备份前那个数据库叫什么
  
  好了,CTRL+ENTER, 如果不行请检查,如果再不行,请重启,如果再不行,。。。阿门,上帝保佑你

3):实例
  备份文件路径:C:\Temp\ipmdemo.0\DB2CTLSV\NODE0000\CATN0000\20090622\164642.001
其中,备份日期:20090622 备份文件:164642.001
  新建数据库名:ipmdemo
  数据库文件时间戳: 20090622164642

恢复命令为:db2 restore database ipmdemo from C:\Temp taken at 20090622164642

3、恢复在线备份

1)、恢复。恢复命令为:db2 restore database DBName from C:\Temp taken at 20090622164642

2)、前滚。前滚命令为:db2 rollforward db DBName to end of logs(或者:控制中心->所有数据库->dbname ->右键->前滚)

3)、停止前滚。停止前滚命令为:db2 rollforward db DBName stop(或者:控制中心->所有数据库->dbname ->右键->停止前滚)

三、有关说明

1、恢复操作也有online和offline的,区别如同backup的操作。
2、按照表空间的备份和恢复类似,加子句TABLESPACE ( tablespace-name )即可。表空间级别的备份/恢复操作要求数据库处于归档日志和启用增量备份模式下。
3、恢复的例子中只做了版本恢复。若还有更新的全备份和增量备份的image,可以依次做恢复(注意使用db2ckrst的建议恢复次序和次数)后,再做roll forward。

本文转载自IBM工程师孙岳的DB2 备份与恢复性能优化及其在 SAP 系统中的实践
转载目的:

  1. 文章确实写好,对于DB2的备份恢复学习很有帮助,非常感谢孙岳
  2. 用于Markdown练手,学习行文格式

本文以基于 DB2 的 SAP 系统为例,详细介绍了 DB2 备份和恢复的工作原理,以及 DB2 备份和恢复中可以调整的优化参数,并通过 SAP 客户备份和恢复实例来进一步说明如何优化 DB2 备份与恢复的性能,最后讨论了在不同容量下的数据库备份和恢复策略。

DB2 备份和恢复简介

数据库运行过程中可能会遇到诸如存储介质损坏,服务器故障,供电中断等不可预测的问题导致数据丢失或损毁。因此,采取一定备份和恢复策略就显的尤为重要。随着企业用户数据量的不断增长,如何快速而有效的对数据进行备份和恢复,就成为数据库日常维护的重要议题。这也是本文讨论的重点。

下面我们简单介绍 DB2 提供的数据库备份和恢复的方法和命令。DB2 数据库通过 BACKUP DATABASE 和 RESTORE DATABASE 命令来进行数据库的备份和恢复。例如:

db2 “backup database HIA online to E:\backup compress include logs”
db2 “restore database HIA from E:\backup

DB2 的备份和恢复命令已经被集成进 DB2 引擎。它能够提供不同粒度和不同级别的备份和恢复:

  • 完整的数据库或者某个表空间的备份与恢复
  • 离线或在线的数据库备份
  • 完整、增量或者 delta 方式的数据库备份与恢复

同时,DB2 备份和恢复命令还具有一系列的参数用来调整数据库备份和恢复的效率。例如我们可以通过 UTIL_IMPACT_PRIORITY 参数来调整 DB2 在备份过程中的 CPU 占用率。由于 DB2 备份恢复的参数优化与 DB2 备份和恢复的进程模型具有密切的关系,所以我们首先来看一下 DB2 备份和恢复的工作原理。

DB2 备份和恢复的工作原理

DB2 备份和恢复主要由两种不同的 EDU 共同完成数据库的备份和恢复操作。其中 db2bm(备份和恢复缓冲区操纵程序)用来在表空间和系统缓存间进行数据传输,db2med(备份和恢复介质控制器)用来在系统缓存和外部介质之间进行数据传输。db2bm 和 db2med 均在 db2agent 的协调下进行工作。其中,db2bm 需要通过 db2pfchr(缓冲池预取进程)从容器中预取数据,或者通过 db2pclnr(缓冲池页清楚程序)向容器中写入数据。在 DB2 V9.5 版本之前,db2bm 和 db2med 是以进程方式存在于内存中,而在 DB2 V9.5 版本后改为线程方式。DB2 备份进程模型如下图1所示。
图 1. DB2 备份进程模型

以 DB2 备份为例,从图中我们可以看出,其存在 3 个表空间(tablespace1,tablespace2,tablespace3)和 3 个外部存储介质。第一个 db2bm 进程负责从 tablespace1 和 tablespace2 中顺次读取数据,并将它们写入到系统缓存中。即 db2bm 进程会首先对 tablespace1 进行备份,当备份完成后,db2bm 会开始对 tablespace2 的备份。第二个 db2bm 进程负责从 tablespace3 中读取数据并写入系统缓存。同时,每一个外部存储介质对应于一个 db2med 进程,db2med 进程负责将系统缓存中的数据写入到外部设备中。

DB2 恢复过程与 DB2 备份过程类似,在这里就不再重复解释。如下图 2 所示。
图 2. DB2 恢复进程模型

对于 DB2 备份和恢复过程来说,在 EDU 内部,DB2 并不会控制 db2bm 进程从表空间往系统缓存的写入或读取速度,同样的,db2med 进程在系统缓存和外部介质之间的传输速度也不会受到限制。DB2 备份和恢复工具会以尽可能快的速度向外部介质写入和读取数据。我们只能够通过外部参数 UTIL_IMPACT_PRIORITY 对 DB2 备份和恢复进行整体控制。

在 DB2 备份和恢复过程中,每一个表空间都会由一个单独的 db2bm 进程负责读取与写入。如图 1 所示,如果备份过程中存在有 2 个 db2bm 进程,那个其中一个 db2bm 进程会负责对其中的两个表空间进行读写操作。并且这 2 个 db2bm 进程会并行的对表空间进行读写操作,如 tablespace1 和 tablespace3 中的数据会由不同的 db2bm 并行写入到系统缓存。db2bm 的数量可以有 PARALLELISM 参数进行指定,如下。

db2 backup database to d:\backup PARALLELISM 4
db2 restore database from d:\backup PARALLELISM 4

同理,我们可以通过如下的方式指定 db2med 的数目。

db2 backup database to d:\backup e:\backup f:\backup
db2 restore database from d:\backup e:\backup f:\backup

DB2 会根据我们所指定的目标路径的数据来生成对应数据的 db2med 进程。

DB2 备份和恢复的优化参数

本节我们将详细介绍影响 DB2 备份和恢复的优化参数,并结合工作原理解释它们的具体含义,并给出性能优化建议。

BACKUP 优化参数

在 DB2 备份命令中有如下参数可以用来调整数据库备份的性能。

BACKUP DATABASE <DB> TO dir | dev WITH num-buf BUFFERS 
BUFFER buff-size PARALLELISM n COMPRESS 
UTIL_IMPACT_PRIORITY priority

PARALLELISM n

通过使用 PARALLELISM 参数指定了使用 db2bm 进程数目,它决定了从表空间中往系统缓存中写入的并行性。每一个 db2bm 进程会对应于一个或多个表空间,当一个 db2bm 进程完成对当前表空间的备份后,它会继续对另外一个表空间进行备份。由于每一个 db2bm 进程至少对应于一个表空间,因此 PARALLELISM 值需要小于数据库表空间的数目,如果将设置 PARALLELISM 为大于数据库中表空间的数目,其并不会显著提高备份的性能。

To dir | dev

如果有多个目标路径被指定,如 To dir1, dir2, dir3,则可以指定备份时使用的 db2med 的进程数目。多个 db2med 进程会并行的像 dir1, dir2, dir3 中写入数据,其实第一个目标路径 dir1 中会保存备份数据的数据头以及一些特殊的文件。

如果系统中有多个磁盘系统,可以使用多个目标路径分别指向这些磁盘系统,以提高往磁盘写入数据的并行性。

BUFFER buff-size

使用 BUFFER buff-size 参数可以指定内部缓存的大小,如图 1 中的内部缓存(Utility Heap),单位为 4KB。如果 BUFFER 的值没有指定,则使用 dbm 中的参数 BACKBUFSZ 作为内部缓存的大小。进行备份时,数据首先由表空间写入内部缓存,当缓存满后,缓存中的数据会被写入到外部磁盘中。

BUFFER 值的大小建议设置为 extent 大小的整数倍。如果不同的表空间的 extent 大小不同,则 BUFFER 值建议为它们的最小公倍数。

WITH num-buf BUFFERS

如图 1 所示,每一个备份过程可以拥有一个或者多个内部缓存。将内部缓存的数目设置为至少 db2med 进程的 2 倍能够有效的提高备份时的速度,它使得 db2med 进程向外部磁盘写入数据时无需等待缓存。

同时需要注意的是,(BUFFER buff-size) * (WITH num-buf BUFFERS) < UTIL_HEAP_SZ。UTIL_HEAP_SZ 指定了 DB2 中实用程序堆的大小,此参数指定可由备份、复原和装入(包括装入恢复)实用程序同时使用的最大内存量。

UTIL_IMPACT_PRIORITY

UTIL_IMPACT_PRIORITY 可以用来调节备份的速度,其取值在 1 到 100 之间。1 代表最低优先级,100 代表最高优先级。如果此参数不指定,则备份进程会以最快的速度备份数据。

COMPRESS

备份压缩功能能够在数据写入外部磁盘前,对内部缓存中的数据进行压缩,从而减少备份文件的大小。其使用的是一种改进的 Lempel-Zev(LZ)算法。

通过开启数据压缩功能,我们可以节省存储备份的磁盘空间。例如,在下面的例子中,我们将 SAP 的备份的容量从 89GB 缩小到 13GB。

RESTORE 优化参数

在 DB2 恢复命令中有如下参数可以用来调整数据库恢复的性能。

RESTORE DATABASE <DB> FROM dir | dev WITH num-buf 
BUFFERS BUFFER buff-size PARALLELISM n

PARALLELISM n

与 DB2 备份命令相同,PARALLELISM 参数用来指定在恢复过程中的 db2bm 的数目。通过增加 PARRALLELISM 值可以提高数据在内部缓存和表空间之间的并行性。

FROM dir | dev

如果指定多个备份存储路径,(其取决于备份时目标路径的数目)如 From dir1, dir2, dir3,则可以指定恢复时 db2med 进程数目的多少,从而加快数据从外部介质写入内部缓存的速度。

BUFFER buf-size

BUFFER buf-size 的大小为一块内部缓存的大小,其应该设置为 extent 大小的整倍数,同时需要等于或者为备份时所定义大小的整倍数。如果没有设置的话,则使用 dbm 中的 RESTBUFSZ 参数作为内部缓存的大小。

WITH num-buff BUFFERS

指定使用的内部缓存的大小,同备份参数相同,其最好设置为 db2med 的 2 倍以提高备份的速度。同时 (BUFFER buff-size) * (WITH num-buf BUFFERS) < UTIL_HEAP_SZ。

我们可以看到,通过调整 DB2 备份和恢复的参数,我们可以根据当前系统所处的不同硬件环境调整数据库备份和恢复过程中的 db2bm(备份和恢复缓冲区操纵程序)和 db2med(备份和恢复介质控制器)的数目,以及它们在备份和恢复过程中使用的内部缓存的大小和数目等参数,从而提高 DB2 的整体备份和恢复性能。

那么,在具体的环境中,我们如何利用这些参数以提高备份和恢复的性能,就成为我们下面讨论的重点。下一节我们以基于 DB2 的 SAP 系统为例,讨论如何在 SAP 系统中提高备份和恢复的性能,进一步详细说明性能参数在数据库备份和恢复中的影响。

实例 – 在 SAP 系统中进行快速有效的备份和恢复

本节以基于 DB2 的 SAP 系统的备份和恢复为例,通过备份恢复时间,数据库备份和恢复参数的调整与比较等,进一步说明性能参数在数据库备份和恢复中的影响。下文进行备份和恢复的系统硬件环境为 AMD 4 核 CPU,8GB 内存,2 块 IBM SCSI 磁盘作为备份介质,而 SAP 数据库存储在另外 1 块 RAID5 磁盘上。
SAP 系统的备份

通常,SAP 管理员会通过 SAP 中的 DBACockpit 中的 DBA Planning Calendar 对 SAP 的数据库进行备份,或者可以通过业务代码 DB13 直接进入到 DBA Planning Calendar 页面。在 DBA Planning Calendar 里选取 Database Backup to Device 弹出选择画面,如图 3 所示。
图 3. Database Backup to Device 备份选择页面

从选择页面中我们可以看到,其提供了备份方式(Backup Mode),备份类型(Backup Type)的选择(关于备份方式和备份类型我们在这里不进行深入的讨论)。其他参数的对应关系如下:

  • 压缩(Compress):对应于 DB2 备份命令中的 COMPRESS 参数。
  • 内部缓存数目(Number of Buffers):对应于 DB2 备份命令中的 WITH num-buff BUFFERS。
  • 内部缓存大小(Buffer Size):对应于 DB2 备份命令中的 BUFFER buf-size,在这里以页为单位(4 KB)。
  • 并行数目(Parallelism):对应于 DB2 备份命令中的 PARALLELISM 参数,通过指定该参数可以调整 db2bm 的数目。
  • 优先级(Priority):对应于 DB2 备份命令中的 UTIL_IMPACT_PRIORITY,通过它我们可以控制 DB2 进行备份的速度。
  • 目标路径(Device/Directory):对应于 DB2 备份命令中的 To dir | dev,通过指定多个目标路径我们可以调整 db2med 的数目。

上面我们已经知道系统具体的硬件环境为 4 核 AMD CPU,8GB 内存,2 块 IBM SCSI 磁盘。那么下面我们通过几组不同的参数比较,来对比一下在不同参数下的 DB2 备份性能。

在备份时,比较明显的提高备份速度的方法是改变 PARALLELISM 的值,如我们分别用表 1 中的参数通过 SAP 备份工具进行备份,其中 PARALLELISM 分别等于 2,4 和 8。运行结果如下图 4 所示。从图中我们可以看到,当我们使用 PARALLELISM 为 2 进行备份时,完成整个在线备份的时间大约为 1 个小时。而当使用 PARALLELISM 为 8 时,我们可以将在线备份的时间缩短到 40 分钟左右。因此,通过增加 PARALLELISM 的参数值在一定程度上是可以提高备份速度。

但是,这也并不意味着 PARALLELISM 的值越大越好。我们需要考虑 CPU 的负载能力。例如在本例中,当我们把 PARALLELISM 的值增加到 16 时,备份时间反而增加到 72 分钟。

表 1. PARALLELISM 参数值

Parallelism Number of Buffers / Buffer Size Device / Directory Compress Priority
2 8 / 1024 D:\backup Y 100
4 8 / 1024 D:\backup Y 100
8 8 / 1024 D:\backup Y 100

图 4. SAP 数据库备份时间 – 调整 PARALLELISM

同样,我们也可以通过增加备份的路径数目来提高备份效率。其备份时的参数和结果如下表 2 和图 5 所示。通过结果可以看出,我们也可以通过增加磁盘数目,从而提高磁盘在单位时间内的 I/O 数,来达到提高备份速度的目的。

表 2. To dir | dev 参数值

Parallelism Number of Buffers / Buffer Size Device / Directory Compress Priority
1 8 / 1024 D:\backup Y 50
1 8 / 1024 D:\backup, F:\backup Y 50

图 5. SAP 数据库备份时间 – 多个存储路径备份

另外,通过使用压缩功能也可以提高数据库的备份速度。如下图 6 所示。从图中我们可以看到,采用压缩功能的备份时间要远远小于不压缩的备份时间。由于 DB2 数据压缩功能是在备份时由数据库对缓存中的数据进行实时的压缩,例如:

  • 采用压缩的备份文件约为 13 GB
  • 不采用压缩的备份文件约为 89 GB

因此它可以有效减小备份文件的大小,即它也减小了备份时对磁盘 I/O 的请求数,因此提高了备份的效率。
图 6. SAP 数据库备份时间 – 压缩和非压缩

SAP 系统的恢复

在企业应用中进行数据库恢复,通常是当系统出现故障或者其他意外情况导致系统不可用。那么这时就需要我们能够快速的将 SAP 数据库恢复,以减少业务损失。因此,通过调整 RESTORE 参数来提高恢复性能就成为我们十分重要的选择。

我们通过以下的一些实验数据来对调整 RESTORE 参数进行进一步的说明。硬件环境与备份系统的环境相同。

当我们进行 SAP 数据库恢复时,我们可以通过改变 PARALLELISM 的值来提高 DB2 数据库恢复的速度。如图 7 所示,通过如下命令对 SAP 系统的数据库分别进行恢复。我们可以看到,当 PARALLELISM 设置为 8 时(即系统中有 8 个 db2bm 进程同时从表空间读取数据)的恢复时间几乎为 PARALLELISM 等于 1 时的一半。

RESTORE DATABASE HIA FROM d:\bkup WITH 8 BUFFERS BUFFER 1024 PARALLELISM
RESTORE DATABASE HIA FROM d:\bkup WITH 8 BUFFERS BUFFER 1024 PARALLELISM
RESTORE DATABASE HIA FROM d:\bkup WITH 8 BUFFERS BUFFER 1024 PARALLELISM

图 7. SAP 数据库恢复时间 – 调整 PARALLELISM

另外,我们也可以通过从多个存储路径中进行数据库恢复,从而提高数据库的恢复速度。如下图 8 所示。我们通过如下的命令从备份文件中恢复数据库。从图 7 中的数据我们可以看出,同时从两个不同的磁盘目录中进行恢复的速度在一定程度上要优于从单一磁盘目录中进行恢复。其原因是在同一时刻,我们可以通过多个磁盘来获取更多的 I/O 数,从而提高备份数据从磁盘读取的速度。但是,这也取决于磁盘每秒钟的 I/O 数和磁盘传输数据的速度,因此,在某些情况下,从多个不同磁盘目录中恢复数据不一定要优于从单一磁盘中恢复。

RESTORE DATABASE HIA FROM d:\bkup
RESTORE DATABASE HIA FROM d:\bkup, f:\bkup

图 8. SAP 数据库恢复时间 – 从多个存储路径恢复

不同容量下的 DB2 备份和恢复策略

上面我们通过 SAP 系统的备份和恢复实例给出了在不同参数下的 DB2 数据库的备份和恢复性能。但是在企业的实际应用中,通过调整 DB2 的备份和恢复参数不一定能够达到理想的效果。通常,对于不同容量的数据库,我们也需要制定不同的数据库备份和恢复策略,使得我们能够对数据库进行快速的备份和恢复。SAP 推荐的 DB2 数据库备份恢复策略如下图 9 所示。

从下图我们可以看到,SAP 的恢复策略按容量分为小规模应用,中等规模容量,大容量以及极大规模应用:

  • 对于开发和测试的 SAP 数据库,我们只需要每周对其进行一次在线备份即可。
  • 对于中等规模容量的 SAP 生产系统来说,则我们需要每天进行在线备份。由于它的数据库大小有限,所以一般来说,我们在几个小时之内可以完成对它的在线备份。
  • 当数据库容量上升到大规模应用时,那么每天进行在线备份就不是一个好的解决方案。在这种情况下,我们可能很难在几个小时之内完成对数据库的备份,并且会影响到白天正常的业务运转。因此,在每天使用增量备份(Incremental Backup)就成为一种好的选择,它可以减少所需备份的数据量,从而提高备份的速度。同时,我们需要在每个周末进行一次在线备份来对数据库进行完整的备份。
  • 当数据库容量继续上升后,使用增量备份也难以对数据库进行快速的备份,那么这时我们就需要使用分割镜像(Split Mirror)技术对数据库进行实时的备份。
    图 9. SAP 数据库备份恢复策略

结束语

本文介绍了 DB2 数据库备份和恢复的基本工作原理,并对 DB2 数据库备份和恢复的优化参数进行了详细的解释,同时通过在 SAP 系统中对 DB2 数据库的备份和恢复的实例,进一步说明了通过调整优化参数,能够提高在大数据容量下的数据库备份和恢复效率。最后,我们也给出了在不同数据库容量下的数据库备份和恢复策略,供需要规划 SAP 和 DB2 系统备份和恢复的读者参考。

参考资料

学习

获得产品和技术

  • 使用可直接从 developerWorks 下载的 IBM 产品评估试用软件 构建您的下一个开发项目。
  • 现在可以免费使用 DB2。下载 DB2 Express-C,这是为社区提供的 DB2 Express Edition 的免费版本,它提供了与 DB2 Express Edition 相同的核心数据特性,为构建和部署应用程序奠定了坚实的基础。

讨论

  • 通过 SAP COMMUNITY NETWORK:获得 SAP 家族产品和文档资源。
  • 通过 SAP SERVICES MARKETPLACE:获得 SAP 产品和服务。SAP 客户可以通过 SAP 客户 ID 访问 SAP NOTES。
  • 参与 developerWorks blogs 并加入 developerWorks 中文社区,查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。

这里我们用一个学生成绩表作为例子,对HBase的基本操作和基本概念进行讲解:
下面是学生的成绩表:

name grade course:math course:art
Tom 1 87 97
Jerry 2 100 80

这里grade对于表来说是一个列,course对于表来说是一个列族,这个列族由两个列组成:math和art,
当然我们可以根据我们的需要在course中建立更多的列族,如computer,physics等相应的列添加入course列族.
有了上面的想法和需求,我们就可以在HBase中建立相应的数据表啦!

  1. 建立一个表格 scores 具有两个列族grade 和courese

    hbase(main):002:0> create 'scores', 'grade', 'course'
    0 row(s) in 4.1610 seconds
    
  2. 查看当先HBase中具有哪些表

    hbase(main):003:0> list
    scores
    1 row(s) in 0.0210 seconds
    
  3. 查看表的构造

    hbase(main):004:0> describe 'scores'
    {NAME => 'scores', IS_ROOT => 'false', IS_META => 'false', FAMILIES => [{NAME => 'course', BLOOMFILTER => 'false', IN_MEMORY => 'false', LENGTH => '2147483647', BLOCKCACHE => 'false', VERSIONS => '3', TTL => '-1', COMPRESSION => 'NONE'}, {NAME => 'grade', BLOOMFILTER => 'false', IN_MEMORY => 'false', LENGTH => '2147483647', BLOCKCACHE => 'false', VERSIONS => '3', TTL => '-1', COMPRESSION => 'NONE'}]}
    1 row(s) in 0.0130 seconds
    
  4. 加入一行数据,行名称为 Tom 列族grad的列名为”” 值位1

    hbase(main):005:0> put 'scores', 'Tom', 'grade:', '1'
    0 row(s) in 0.0070 seconds
    
  5. 给Tom这一行的数据的列族添加一列

    hbase(main):006:0> put 'scores', 'Tom', 'course:math', '87'
    0 row(s) in 0.0040 seconds
    
  6. 给Tom这一行的数据的列族添加一列

    hbase(main):007:0> put 'scores', 'Tom', 'course:art', '97'
    0 row(s) in 0.0030 seconds
    
  7. 加入一行数据,行名称为 Jerry 列族grad的列名为”” 值位2

    hbase(main):008:0> put 'scores', 'Jerry', 'grade:', '2'
    0 row(s) in 0.0040 seconds
    
  8. 给Jerry这一行的数据的列族添加一列

    hbase(main):009:0> put 'scores', 'Jerry', 'course:math', '100'
    0 row(s) in 0.0030 seconds
    
  9. 给Jerry这一行的数据的列族添加一列

    hbase(main):010:0> put 'scores', 'Jerry', 'course:art', '80'
    0 row(s) in 0.0050 seconds
    
  10. 查看scores表中Tom的相关数据

    hbase(main):011:0> get 'scores', 'Tom'
    COLUMN                       CELL
    course:art                  timestamp=1224726394286, value=97
    course:math                 timestamp=1224726377027, value=87
    grade:                      timestamp=1224726360727, value=1
    3 row(s) in 0.0070 seconds
    
  11. 查看scores表中所有数据

    hbase(main):012:0> scan 'scores'
    ROW                          COLUMN+CELL
    Tom                         column=course:art, timestamp=1224726394286, value=97
    Tom                         column=course:math, timestamp=1224726377027, value=87
    Tom                         column=grade:, timestamp=1224726360727, value=1
    Jerry                        column=course:art, timestamp=1224726424967, value=80
    Jerry                        column=course:math, timestamp=1224726416145, value=100
    Jerry                        column=grade:, timestamp=1224726404965, value=2
    6 row(s) in 0.0410 seconds
    
  12. 查看scores表中所有数据courses列族的所有数据

    hbase(main):013:0> scan 'scores',{COLUMN=>['course']}        
    ROW                                      COLUMN+CELL                                                                                                        
     Jerry                                   column=course:art, timestamp=1441953970540, value=80                                                               
     Jerry                                   column=course:math, timestamp=1441953959676, value=100                                                             
     Tom                                     column=course:art, timestamp=1441953935469, value=97                                                               
     Tom                                     column=course:math, timestamp=1441953923125, value=87                                                              
    2 row(s) in 0.0640 seconds
    

上面就是HBase的基本shell操作的一个例子,可以看出,hbase的shell还是比较简单易用的,
从中也可以看出HBase shell缺少很多传统sql中的一些类似于like等相关操作,
当然,HBase作为BigTable的一个开源实现,而BigTable是作为 google业务的支持模型,
很多sql语句中的一些东西可能还真的不需要.
当然,通过程序我们也可以对HBase进行相关的操作.下面的程序就完成了上面shell操作的内容:

import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.util.Map;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.io.BatchUpdate;
import org.apache.hadoop.hbase.io.RowResult;
import org.apache.hadoop.hbase.io.Cell;
import org.apache.hadoop.hbase.util.Writables;
public class HBaseBasic {
    public static void main(String[] args) throws Exception {
        HBaseConfiguration config = new HBaseConfiguration();
        HBaseAdmin admin = new HBaseAdmin(config);
        if (admin.tableExists("scores")) {
            System.out.println("drop table");
            admin.disableTable("scores");
            admin.deleteTable("scores");
        }

        System.out.println("create table");
        HTableDescriptor tableDescripter = new HTableDescriptor("scores".getBytes());
        tableDescripter.addFamily(new HColumnDescriptor("grade:"));
        tableDescripter.addFamily(new HColumnDescriptor("course:"));
        admin.createTable(tableDescripter);
        HTable table = new HTable(config, "scores");

        System.out.println("add Tom's data");
        BatchUpdate tomUpdate = new BatchUpdate("Tom");
        tomUpdate.put("grade:", Writables.getBytes(new IntWritable(1)));
        tomUpdate.put("course:math", Writables.getBytes(new IntWritable(87)));
        tomUpdate.put("course:art", Writables.getBytes(new IntWritable(97)));
        table.commit(tomUpdate);

        System.out.println("add Jerry's data");
        BatchUpdate jerryUpdate = new BatchUpdate("Jerry");
        jerryUpdate.put("grade:", Writables.getBytes(new IntWritable(2)));
        jerryUpdate.put("course:math", Writables.getBytes(new IntWritable(100)));
        jerryUpdate.put("course:art", Writables.getBytes(new IntWritable(80)));
        table.commit(jerryUpdate);
        for (RowResult row : table.getScanner(new String[] { "course:" })) {
            System.out.format("ROW\t%s\n", new String(row.getRow()));
            for (Map.Entry<byte[], Cell> entry : row.entrySet()) {
                String column = new String(entry.getKey());
                Cell cell = entry.getValue();
                IntWritable value = new IntWritable();
                Writables.copyWritable(cell.getValue(), value);
                System.out.format(" COLUMN\t%s\t%d\n", column, value.get());
            }
        }
    }
}

输出如下:

drop table
09/07/11 08:51:59 INFO client.HBaseAdmin: Disabled scores
09/07/11 08:51:59 INFO client.HBaseAdmin: Deleted scores
create table
add Tom's data
add Jerry's data
ROW     Tom
  COLUMN        course:art      97
  COLUMN        course:math     87
ROW     Jerry
  COLUMN        course:art      80
  COLUMN        course:math     100

HBase基本类型定义

  • Table:表
  • RowKey:行健,主键
  • Column Family:列族,包含一个或者多个相关列
  • Column:属于某一个columnfamily,familyName:columnName,每条记录可动态添加
  • timestamp:每次操作对应的时间戳,支持用户自定义,默认为当前时间的毫秒值
  • value:值,和timestamp一起支持多version的概念

通过HBase Shell可以拿到一条数据,如下:

hbase(main):029:0> scan 'scores',{LIMIT=>1}
ROW                                      COLUMN+CELL                                                                                                        
 Jerry                                   column=course:art, timestamp=1441953970540, value=80                                                               
 Jerry                                   column=course:math, timestamp=1441953959676, value=100                                                             
 Jerry                                   column=grade:, timestamp=1441953949725, value=2                                                                    
1 row(s) in 0.0350 seconds

对应内容:

Jerry =》 RowKey
course =》 Column Family
column =》 art
timestamp => 1441953970540
80  => value

HBase存储结构

  1. HBase以表(HTable)的形式存储数据
  2. HTable包括很多行,每行通过RowKey唯一标记,行按照RowKey的字典序排列,表在行的方向上分割为多个HRegion
  3. 每行包括一个RowKey和多个Column Family,数据按照Column Family进行物理切割,即不同Column Family的数据放在不同的Store中,一个Column Family放在一个Strore中
  4. HRegion由多个Store组成。一个Store由物理上存在的一个MemStrore(内存中)和多个StoreFile(HFile)中

设计

从应用角度,有两点比较重要:

  1. HBase中RowKey是按照字典序排列的
  2. 不同Column Family的数据,在物理上是分开的

在做table设计的时候,主要围绕上述两点做文章。
RowKey的设计需要根据请求数据特点:

  1. 单个查询,需要尽量缩小Key的长度
  2. 范围查询,根据RowKey按字典序排列的特点,针对查询需求设计rowkey,保证范围查询的rowkey在物理上存放在一起

Column Family的设计需遵循:尽量避免一次请求需要拿到的Column分布在不同的Column Family中

实例

对于基于RowKey的范围查询设计,我们来看一个实例:

  1. 给出userid,返回这个userid最近插入的N条数据
  2. 给出userid,及一个时间区间,返回这个时间区间的N条数据

针对需求,Key设计如下:

Userid_DataTime_InertTime
Userid:即userid
DataTime:数据所属时间(ms),定义为:Long.MAX_VALUE - dataTime.getTime(),由于RowKey字典序排列,可以使最近插入的数据排在前面,支持“最近插入的N条数据”的需求
InsertTime:数据入库时间(ns),取nanotime(InsertTime的存在是由于在这个应用中,Userid+DataTime不能唯一定位一条数据)

Key生成代码如下:

//生成RowKey  
private String buildPutRowKey(int userId, Date addTime) {  
        String key = userId + "_" + getRowKeyTimestamp(addTime) + "_" + System.nanoTime();  
        return key;  
}  

//构建DataTime  
private long getRowKeyTimestamp(Date addTime) {  
        return Long.MAX_VALUE - addTime.getTime();  
}  

Table中Family和Qualifier的关系与区别

就像用MySQL一样,我们要做的是表设计,MySQL中的表,行,列的在HBase已经有所区别了,在HBase中主要是Table和Family和Qualifier,这三个概念。Table可以直接理解为表,而Family和Qualifier其实都可以理解为列,一个Family下面可以有多个Qualifier,所以可以简单的理解为,HBase中的列是二级列,也就是说Family是第一级列,Qualifier是第二级列。两个是父子关系。

谈谈Table中Family和Qualifier的设置

对于传统关系型数据库中的一张table,在业务转换到hbase上建模时,从性能的角度应该如何设置family和qualifier呢?
最极端的,可以每一列都设置成一个family,也可以只有一个family,但所有列都是其中的一个qualifier,那么有什么区别呢?
family越多,那么获取每一个cell数据的优势越明显,因为io和网络都减少了,而如果只有一个family,那么每一次读都会读取当前rowkey的所有数据,网络和io上会有一些损失。
当然如果要获取的是固定的几列数据,那么把这几列写到一个family中比分别设置family要更好,因为只需一次请求就能拿回所有数据。
以上是从读的方面来考虑的,那么写呢?可以参考一下这篇文章:
http://hbase.apache.org/book/number.of.cfs.html

首先,不同的family是在同一个region下面。而每一个family都会分配一个memstore,所以更多的family会消耗更多的内存。
其次,目前版本的hbase,在flush和compaction都是以region为单位的,也就是说当一个family达到flush条件时,该region的所有family所属的memstore都会flush一次,即使memstore中只有很少的数据也会触发flush而生成小文件。这样就增加了compaction发生的机率,而compaction也是以region为单位的,这样就很容易发生compaction风暴从而降低系统的整体吞吐量。
第三,由于hfile是以family为单位的,因此对于多个family来说,数据被分散到了更多的hfile中,减小了split发生的机率。这是把双刃剑。更少的split会导致该region的体积比较大,由于balance是以region的数目而不是大小为单位来进行的,因此可能会导致balance失效。而从好的方面来说,更少的split会让系统提供更加稳定的在线服务。
上述第三点的好处对于在线应用来说是明显的,而坏处我们可以通过在请求的低谷时间进行人工的split和balance来避免掉。
因此对于写比较多的系统,如果是离线应该,我们尽量只用一个family好了,但如果是在线应用,那还是应该根据应用的情况合理地分配family。

HBase实例代码

/**
 * Hbase 基本CRUD 样例代码   覆盖Put Get Delete checkAndPut checkAndDelete  Scan
 * 通过上面的各种操作的例子, 会基本覆盖Htable可以用的的所有方法
 * 这里不涉及Hbase 管理代码的操作
 * @author Administrator
 *
 */
public class HbaseCRUDTest_New {
    private static org.apache.hadoop.conf.Configuration conf = null;
    private static HTablePool pool = null;
    private static HBaseAdmin admin = null;
    private static final int MAX_TABLE_COUNT = 10;

    @BeforeClass
    public static void before()throws Exception{
        /**服务器端缓存客户端的连接 是以conf为单位的(可能不准确:通常一个客户端
         * 连接过来, 服务器端会有一个线程与之对应, 缓存的是这个服务器端的线程),
         * 所以最好不要到处创建conf实例, 一个就够了, 所有共用conf创建的到Hbase
         * 的连接和操作, 会共用一个连接  这样可以提高性能, 也会减小服务器端的压力
         * 实际上创建Htable pool admin都是通过HConnection接口的实现类(
         * HConnectionImplementation)来完成的, 多个HConnection会由
         * HConnectionManager来管理, 而conf是HConnectionImplementation的最
         * 重要的构造参数 , 上面就以conf 来 标识和替代Hconnection 可能会带来歧义
         * 以为conf就是连接本身
         */
        conf = HBaseConfiguration.create();
        //这是一个10台集群的daily 日常性能测试环境
        conf.set("hbase.zookeeper.quorum", "10.232.31.209,10.232.31.210,10.232.31.211");
        conf.set("hbase.zookeeper.property.clientPort", "3325");
        //conf.addResource("dataSource.xml");//也可以载入一个标准的Hbase配置文件

        /**HTable是非线程安全的  在多线程环境下使用HTablePool是一个好的解决方案,
         * 参数MAX_TABLE_COUNT 是 pool保持的每个Htable实例的最大数量  ,
         * 比如为10   如果有100个线程getTable() 同一张表   则他们会共用 pool中的该
         * 表的10个实例   有些可能要排队等 用完的要回收放回去
         * 使用的时候 就不要new Htable了, 直接从pool中取
         * 用完再putTable 放回去
         *
         * 在0.92以上的版本  则不用放回去   直接table.close() 即可    putTable 被标记
         * 为 @Deprecated.  0.90.2 版本使用 putTable 下面的代码都没有 做 这些操作
         * 避免 不同版本 出问题
         */
        pool = new HTablePool(conf, MAX_TABLE_COUNT);
        admin = new HBaseAdmin(conf);
    }
    /**
     * 注意一下:Put Get Delete Scan等操作的对象 都提供一个空的构造函数, 一般不要直接使用, 
     *          他们存在主要是在rpc传输的反序列化的时候要用到(了解Java RMI的应该很清楚)
     * @throws IOException
     * @throws InterruptedException
     */
    @Test public void putTest() throws IOException, InterruptedException{
        HTable table = (HTable)pool.getTable("user_test_xuyang");
        //批量操作 共两种 底层都是调用  HConnection的processBatch方法(
        // table.batch(List<Put>) 和table.flushCommits()会直接调用)

        //首先  自动flush 关闭    就像 JDBC中的 auto_commit,  否则 加每一条 提交
        // 一次,影响性能     不过table.put(List<Put>) table.batch(List<Row>)不受这
        // 个影响, 设置false,只有当put总大小超过writeBufferSize 才提交  或者手工
        // table.flushCommits() (table.put(List<Put>)操作完成后会手工提交一次),
        // writeBufferSize 也可以调整
        table.setAutoFlush(false);
        //writeBufferSize 默认为2M ,调大可以增加批量处理的吞吐量, 丢失数据的风险也会加大
        table.setWriteBufferSize(1024*1024*5);
        //这样可以看到 当前客户端缓存了多少put
        ArrayList<Put> putx = table.getWriteBuffer();

        // 批量操作方法一,单一操作的批量 比如Htable.put delete get 都提供了List作
        // 为参数的批处理.   默认每10条 或List<Put>数据量 超过writeBufferSize 提交
        // 如果AutoFlush为true 一次性table.put(List<Put>)只提交一次
        List<Put> puts = new ArrayList<Put>(10);

        for (int i = 0,len=10; i < len; i++) {
            Put put = new Put(Bytes.toBytes("row-"+i),new Date().getTime());
            put.add(Bytes.toBytes("data"), Bytes.toBytes("name"), Bytes.toBytes("value"+i));
            // 这里可以自定义添加时间戳, 默认就是当前时间(RegionServer服务器端的
            // 时间) 也可以自己定义, 多版本时候(默认3)比如想插入一条比现在最新的记
            // 录老的, 一些特殊情况下可能会有这种需求
            put.add(Bytes.toBytes("data"), Bytes.toBytes("email"), System.currentTimeMillis(),
                    Bytes.toBytes("value"+i+"@sina.com"));
            //也可以直接加入一个KeyValue,实际上底层就是存储为KeyValue的, 如果对
            // 底层较熟悉, 这种操行更加高效, 一般上面的就可以完成日常工作了
            put.add(new KeyValue(Bytes.toBytes("row-"+i), Bytes.toBytes("data"),
                    Bytes.toBytes("age"),Bytes.toBytes(20+i)));
            puts.add(put);

            //写操作日志  这个对性能影响比较大,  但有很重要, 如果设为true, 只要写
            // 成功, 就算 机器挂掉 也不会丢失,
            put.setWriteToWAL(false);
            /**
             * Put还有一些额外的东西
             */
            //put.has(family, qualifier,ts,value)
            //put 当前在内存中的大小  这个在setWriteBufferSize 可能会用到
            /**实际上底层是 这么干的(当然还有其他比如put数量对table.flushCommits()的触发)
             * for(Put put:puts){
             *  total+=put.heapSize();
             *  if(total>=table.getWriteBufferSize())
             *      table.flushCommits();
             * }
             */
            put.heapSize();
            //put 中 每次调用add 底层都会添加一个KeyValue,这个是添加的KeyValue数量
            put.size();

            //判断put中是否已经存在了 给定的family qualifier ts value
//          put.has(family, qualifier)
//          put.has(family, qualifier, value)
//          put.has(family, qualifier, ts)
//          put.has(family, qualifier, ts, value)

            //下面的方法 从字面上基本上就可以知道
            put.isEmpty();
            put.getRow();
            put.getRowLock();
            put.getLockId();
            put.numFamilies();

        }

        table.put(puts);
        table.flushCommits();
        System.out.println(table.get(new Get(Bytes.toBytes("row-1"))));
        admin.flush("user_test_xuyang");
        System.out.println(table.get(new Get(Bytes.toBytes("row-1"))));

        //批量操作方法一, 使用batch,可以混合各种操作 ( Put Delete Get 都是接口Row的实现)
        //主要 这个如果处理Put操作 是不会使用客户端缓存的   会直接异步的发送到服务器端
        List<Row> rows = new ArrayList<Row>(10);
        for (int i = 10,len=20; i < len; i++) {
            Put put = new Put(Bytes.toBytes(("row-"+i)));
            put.add(Bytes.toBytes("data"), Bytes.toBytes("name"), Bytes.toBytes(("value"+i)));
            put.add(Bytes.toBytes("data"), Bytes.toBytes("email"), Bytes.toBytes(("value"+i+"@sina.com")));
            rows.add(put);
        }
        //可以添加 删除操作   但是 最好不要把对同一行的Put Delete用batch操作 ,
        // 因为 为了更好的性能  发到服务器端操作的顺序  是会改变的   很有可能不是放入的顺序
        rows.add(new Delete(Bytes.toBytes("row-9")));
        table.batch(rows);

            /**  一些需要注意的地方:
             * 1. 提交到服务器  处理如果出现问题  会从服务器端返回RetriesExhaustedWithDetailsException
             * 包含出错的原因 和重试的次数
             * 如果 服务器端还是操作失败 , 这些put还会缓存在客户端  等到下次buffer 被flush,
             * 注意  如果客户端挂掉了   这些数据是会丢失的
             * 当然如果是NoSuchColumnFamilyException只会重试一次 并且不会恢复
             * 下面的情况要注意了
             * table.put(puts); 是会抛出异常的,而且不会再提交  这样数据会丢失的
             * 捕获这个异常手工table.flushCommits() 可以确保已经写入缓存的还可以有可能写入成功
             * try {
                    table.put(puts);
                } catch (Exception e) {
                    table.flushCommits();
                }
             * table.flushCommits(); 也会有异常   也要捕获
             *
             * 2. 还时候 启用缓存   正常操作发生异常时候并不会被正常报出来, 有时候
             * 会等到buffer被flush后才报出来  这也是要注意的地方
             *
             * 3.在缓存中的puts 被发送到服务器端的顺序和服务器处理的顺序 是控制不
             * 到的, 如果想指定顺序 , 只能使用较小的批处理  强制他们按照批处理的顺序执行
             */

        /**
         * 完备的一条记录就是一个KeyValue 一个rowkey可能有多个KeyValue(比如
         * 多个版本, 一个版本是一条)
         * rowkey ColumnFamily  Column  TimeStamp Type Value
         * 其中的Type就是区别Put和Delete等操作的类别, 实际上Delete也是添加一条记录
         * (Hbase存储的HDFS文件是只读的, 更新用 添加+删除 组合完成, 删除实际上
         * 也是添加一条删除,实际操作都是添加,在Hbase Compact时候 合并数据时候会剔除标记为删除的rowkey)
         * 这种 增 改 删的一致性操作  在客户端给我们的操作带来了便利
         *
         * 实际上ColumnFamily Column的名字是会以byte的形式存储在数据中的,
         * 因此, 它们在设计的时候名字应该尽可能的短 这样可以节省不少的空间
         */
    }

    /**
     * Delete与Put一致 把全部的Put改成Delete  table.put -->table.delete 就可以了,
     * 不过有些需要注意, 看下面
     */
    @Test public void deleteTest(){
        HTable table = (HTable)pool.getTable("user_test_xuyang");
        try {
            //如果上面介绍的KeyValue 有点印象, 通过delete提供的构造函数可以知道
            //不指定会删除所有的版本
            Delete delete = new Delete(Bytes.toBytes("row-1"));
            table.delete(delete);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 一些原子性操作   对于java并发工具包有所了解的 应该会知道 轻量级锁的核心就是CAS机制(Compare and swap),
     * 这里在概念上有些类似, 也可以类似于  SQL中  select 出来然后   insert or update的 操作  
     * Hbase这里可以保证他们在一个原子操作
     * 这个在高并发 场景下  更新值  是个好的选择
     * table.checkAndPut(row, family, qualifier, value, put)
     * table.checkAndDelete(row, family, qualifier, value, delete)
     * @throws IOException
     */
    @Test public void atomicOP() throws IOException{
        byte[] row = Bytes.toBytes("row-12");
        byte[] family = Bytes.toBytes("data");

        HTable table = (HTable)pool.getTable("user_test_xuyang");
        //操作成功会返回 true,否则false;  如果是个不存在的qualifier, 把value置为null  check是会成功的
        Put put = new Put(row);
        put.add(family, Bytes.toBytes("namex"), Bytes.toBytes("value12"));
        //check 和put是同一个row
        boolean result1 = table.checkAndPut(row, family, Bytes.toBytes("namex"), null, put);  //true
        boolean result2 = table.checkAndPut(row, family, Bytes.toBytes("namex"), null, put);   //false

        Put put2 = new Put(row);
        put2.add(family, Bytes.toBytes("namex"), Bytes.toBytes("value12"));
        boolean result3 = table.checkAndPut(row, family, Bytes.toBytes("namex"),
                Bytes.toBytes("value12"), put2);  //true

        Put put3 = new Put(Bytes.toBytes("row-13"));
        put3.add(family, Bytes.toBytes("namex"), Bytes.toBytes("value13"));
        boolean result4 = table.checkAndPut(row, family, Bytes.toBytes("namex2"),
                Bytes.toBytes("value12"), put3);  //org.apache.hadoop.hbase.DoNotRetryIOException
        //注意:check 和put的一定要是同一行 否则会报错

//      table.checkAndDelete类似
    }

    /**
     * 上面的一些操作有些方法可能涉及到Row Locks 但并没有说明   这里详细介绍下
     *
     * 一些会使数据发生变化的操作  比如like put(), delete(), checkAndPut()等等 , 操作都是以一个row为单位的,
     * 使用row lock 可以保证  一次性只能有一个客户端修改一个row
     * 虽然 实践中  客户端应用程序 并没有明确的使用lock, 但服务端会在适当的时机保护每一个独立的操作
     *
     * 如果可能应当尽量避免使用lock, 就像RSBMS一样会有死锁问题
     * @throws IOException
     */
    @Test public void rowLocksTest() throws IOException{
        HTable table = (HTable)pool.getTable("user_test_xuyang");
        byte[] row = Bytes.toBytes("row-8");
        RowLock lock = table.lockRow(row);
        //.....相关操作
        table.unlockRow(lock);
        //锁有效时间 默认时间是1分钟
    }

    @SuppressWarnings("deprecation")
    @Test public void getTest(){
        HTable table = (HTable)pool.getTable("user_test_xuyang");
        Get get = new Get(Bytes.toBytes("row-10"));
        //默认 get 只会取得最新的记录, 使用下面的方法可以获取其他的版本
        //有两个方法 一个带参数的可以指定版本数量, 可能会抛出异常;另外一个没有
        // 参数, 默认Integer.MAX_VALUE, 不会抛出异常
        get.setMaxVersions();
    //  get.setFilter(filter); get 一般数据比较少比较少使用filter, 在Scan的时候会详细介绍Filter
        //通过get.addColumn提供了各种重载方法, 可以过滤只获取哪些ColumnFamily
        // 和Column,get实现这种过滤只能使用这种方法, 接下来的Scan还可以使用Filter来实现
        get.addColumn(Bytes.toBytes("data"),Bytes.toBytes("email"));

        try {
            Result result = table.get(get);
            //这是一个简单的 获取返回结果的方法, 还有其他的通过遍历Map的方式
            List<KeyValue> values = result.list();
            //由于KeyValue靠近底层, 对于一些一些Offset,Length结尾的方法 可以忽略,
            // 比较感兴趣的可以关注下Hbase的底层存储
            for (KeyValue keyValue : values) {
                StringBuilder sb = new StringBuilder(Bytes.toString(keyValue.getFamily()));
                sb.append(":").append(Bytes.toString(keyValue.getQualifier())).append("--:");
                sb.append(Bytes.toString(keyValue.getValue())).append("  ").append(
                        new Date(keyValue.getTimestamp()).toLocaleString());
                System.out.println(sb.toString());
            }

            //这是另外一种获取返回结果的方式, 这种在Scan的返回多个Result的时候
            // 相对实用, 一个rowkey的都在一起, 一个ColumnFamily的也聚合在一起
            NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> nMap = result.getMap();
            for (Map.Entry<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> entry:nMap.entrySet() ) {
                //entry.getKey()为family key
                String family = Bytes.toString(entry.getKey());
                System.out.print(family+":");
                for (Map.Entry<byte[], NavigableMap<Long, byte[]>> entry2 : entry.getValue().entrySet() ) {
                    // entry2.getKey()为qualifier  当然qualifier有可能为空  这个不是问题  但为null的只能有一个
                    String qualifier = Bytes.toString(entry2.getKey());
                    System.out.print(qualifier+"--:");
                    for (Map.Entry<Long, byte[]> entry3:entry2.getValue().entrySet() ) {
                        //entry3.getKey()为 timestamp  entry3.getValue()为 value
                        System.out.print(Bytes.toString(entry3.getValue())+" "
                                +new Date(entry3.getKey()).toLocaleString());
                    }
                }
            }
            System.out.println("------------------");
            //Get的批处理类似于 SQL中的in操作,但操作起来也相当的简单, 和上面
            // 的Put Delete非常类似,也可以混合使用
            List<Row> rows = new ArrayList<Row>();
            rows.add(new Get(Bytes.toBytes("row-10")));
            rows.add(new Get(Bytes.toBytes("row-11")));
            rows.add(new Put(Bytes.toBytes("row-1222221")));
            try {
                Object[] objs = table.batch(rows);
                for (Object obj : objs) {
                    printKeyValue((Result)obj);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        /**
         * Get获取单个 或随机的几个row 使用起来非常方便, 对于访问多个连续的row
         * 使用下面将要介绍的Scan操作,通常情况下, 完成一个业务 需要多个操作, 而
         * ORM无法将一个业务所有的操作SQL封装在一起, 除非直接使用JDBC
         * Hbase的这种 Put Get Delete 是不是很棒 , 对了没有Update, update的就
         * 是新增一条, 由于有版本, 旧的不会被立即淘汰掉
         */
    }

    public void printKeyValue(Result result){
        List<KeyValue> values = result.list();
        //由于KeyValue靠近底层, 对于一些一些Offset,Length结尾的方法 可以忽略,
        // 比较感兴趣的可以关注下Hbase的底层存储
        for (KeyValue keyValue : values) {
            StringBuilder sb = new StringBuilder(Bytes.toString(keyValue.getFamily()));
            sb.append(":").append(Bytes.toString(keyValue.getQualifier())).append("--:");
            sb.append(Bytes.toString(keyValue.getValue())).append("  ").append(
                    new Date(keyValue.getTimestamp()).toLocaleString());
            System.out.println(sb.toString());
        }
    }

    /**
     * 对于连续记录的顺序访问  就是类似于 最常见的Select操作
     * 实际上Scan 非常类似于Hibernate 的DetachedCriteria, 而scan 使用的Filter就相当于Criteria的Expression或Restrictions
     * 可以实现离线封装查询条件   这个是相当的给力啊
     */
    @Test public void ScanTest(){
        ResultScanner resultScanner = null;
        try {
            /**
             * 如Scan的名字, Scan是在一定的范围内startkey(StartRow)和endkey(StopRow)
             * 之间 顺序的扫描, 配合Filter 可以跳过不满足条件的记录  返回需要的结果
             * 当然startkey和endkey只是标识一个范围, 它们对应的rowkey可能并不存在,
             * 但如果存在(startkey) 扫描的范围是[startkey,endkey),否则就是(startkey,endkey)
             * 可以看到 Scan 有一个包裹Get的构造, 可以利用该get的rowkey作为startkey
             */
            Scan scan  = new Scan();

            /**
             * ResultScanner 就是table scanner返回的结果集, 类似于游标 可以迭代获取结果,
             * batch 就是每次迭代从服务器获取的记录数, 设置太小 会频繁到服务器取数据,
             * 太大 会对客户端造成比较大的压力,  具体根据需要使用 , 正常使用可以不必管
             * 它, 大批量读取可以考虑用它改善性能
             * 这里要注意了: 这个记录数是qualifier不是row, 如果一个row有17个qualifier,
             * setBatch(5),一个row就会分散到4个Result中, 分别持有5,5,5,2个qualifier
             * (默认一个row的所有qualifier会在一个Result中)
             *
             * ColumnPaginationFilter 对于一个Row会在一个Result 但是只返回前面一部分
             *
             * 如果使用FirstKeyOnlyFilter等 不是扫描Row全部的Filter 会有冲突 会有异常抛出 */
            scan.setBatch(10);
            /**发给scanners的缓存的Row的数量, 如果没有设置会使用 HTable#getScannerCaching()的值
             * 一般 越大 Scan速度越快, 但消耗的内存也越大*/
            scan.setCaching(10);
            //简而言之就是  batch 是qualifier column级别的   caching是row级别的

            //RegionServer是否应当缓存 当前客户端访问过的数据块    如果是随机的get 这个最好为false
            scan.setCacheBlocks(true);

            /** Scan 最复杂, 也最有用的就是Filter, 特别是FilterList对Filter进行的组合
             * 这里只先介绍Scan的其他参数 ;对于Filter,后面会单独介绍*/

            //startrow和stoprow 可以改变
            scan.setStartRow(Bytes.toBytes("row-12"));
            scan.setStopRow(Bytes.toBytes("row-1110"));
            scan.setMaxVersions(3);//同Get
            /** 可以指定一个时间范围, 扫描指定时间或时间范围的的记录,  */
            scan.setTimeRange(System.currentTimeMillis()-1000000, System.currentTimeMillis());
            /**
             * 也可以指定timestamp 查询
             */
            scan.setTimeStamp(System.currentTimeMillis());

            /**可以使用Get中类似的方法 来限制获取的ColumnFamily Column*/
            scan.addColumn(Bytes.toBytes(""),Bytes.toBytes(""));

            //Scan中使用最多的还是Filter
            HTable table = (HTable)pool.getTable("user_test_xuyang");
            //看下面
            table.setScannerCaching(1000);
            resultScanner = table.getScanner(scan);
            //这是foreach格式    是调用resultScanner.next()的
            //默认情况下  每次调用next() 都要RPC一下服务器   每个row一次, 即时resultScanner(int nbRows)
            //table.setScannerCaching() 默认是1  可以手工设置  设置后 该table实例的所有scan都有效
            //也可以每个scan单设置定就是上面有说过的scan.setCaching(1024*10); 这个会覆盖table设置的值
            for (Result result : resultScanner) {
                //这里就不多说了   和Get中一样的解析
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            //这样一定要记住 用完close
            if(resultScanner!=null)resultScanner.close();
        }

    }

    /**
     * 高级的Scan,就是Filter中的FilterList  可以组合各个Filter
     * select cf1.column1,cf2.column2* from table_name where rowkey>10 or value like 'xxx%' limit 10
     * 如果上面的SQL解析出来 and 表示MUST_PASS_ALL, or 表示MUST_PASS_ONE
     * 就是下面这个样(虽然理解可能不同,但下图的代码如下:)
     *  ( (cf1 and column1) or (cf2 and column2*)  ) and (rowkey>10 or value like 'xxx%')
     * setFilter(
     *                                                        -ColumnFamilyFilter  cf1
     *                                     -filterList(ALL)--|
     *                                    |                   -ColumnFilter   column1
     *                  -filterList(ONE)->|
     *                 |                  |                   -ColumnFamilyFilter cf2
     *                 |                   -filterList(ALL)--|
     *                 |                                      -ColumnPrefixFilter column2
     *filterList(ALL)->|                -RowFilter 10
     *                 |-filterList(ONE)->|
     *                 |                   -ValueFilter xx
     *                  -PageFilter 10
     * @author Administrator
     *
     */
    @Test public void scanAdvance(){
        Scan scan  = new Scan();
        List<Filter> rootList = new ArrayList<Filter>();
            List<Filter> selectList = new ArrayList<Filter>();
                List<Filter> select_1 = new ArrayList<Filter>();
                    select_1.add(new FamilyFilter(CompareFilter.CompareOp.EQUAL,
                            new BinaryComparator(Bytes.toBytes("cf1"))));
                    select_1.add(new QualifierFilter(CompareFilter.CompareOp.EQUAL,
                            new BinaryComparator(Bytes.toBytes("column1"))));
                List<Filter> select_2 = new ArrayList<Filter>();
                    select_2.add(new FamilyFilter(CompareFilter.CompareOp.EQUAL,
                            new BinaryComparator(Bytes.toBytes("cf2"))));
                    select_2.add(new QualifierFilter(CompareFilter.CompareOp.EQUAL,
                            new BinaryPrefixComparator(Bytes.toBytes("column"))));
            selectList.add(new FilterList(Operator.MUST_PASS_ALL, select_1));
            selectList.add(new FilterList(Operator.MUST_PASS_ALL, select_2));
        rootList.add(new FilterList(Operator.MUST_PASS_ONE,selectList));

            List<Filter> whereList = new ArrayList<Filter>();
                whereList.add(new RowFilter(CompareFilter.CompareOp.GREATER,
                        new BinaryComparator(Bytes.toBytes(10))));
                whereList.add(new RowFilter(CompareFilter.CompareOp.EQUAL,
                        new BinaryPrefixComparator(Bytes.toBytes("xxx"))));
        rootList.add(new FilterList(Operator.MUST_PASS_ONE,whereList));
        scan.setFilter(new FilterList(Operator.MUST_PASS_ALL, rootList));
        //这样的嵌套 写起来着实很烦, 可以自己封装成程序
    }

    /**
     * 一个不得不说的操作  分页操作, 
     * RDBMS 比如mysql :select * from table_name where sss=sss limit 1 10;
     * oracle 利用rownum也可以迂回实现,
     * Hbase这方面支持的不是太好, 也可以支持翻页
     */
    @Test public void pageTest(){
        //与传统的分页的不同  start 是个起始的row  而不是一个数字 ,   下一页 的时候
        // 需要将上一页的最后一条记录作为分页条件传回来
        //这个start要是byte[],页面上只能暂时保存字符串  怎么办呢??
        //Bytes.toStringBinary(byte[])与Bytes.toBytesBinary(String) 可以完美的实现字符串和byte[]的相互转换
        // Bytes.toStringBinary(Bytes.toBytesBinary("abc")) equals "abc" 是true
        byte[] start = Bytes.toBytes("row-13");
        int limit = 10;
        Scan scan = new Scan();
        scan.setStartRow(start);

        List<Filter> rootList = new ArrayList<Filter>();
        rootList.add(new PageFilter(limit));
        ////root.add(new Filter()) 添加其他的过滤条件

        scan.setFilter(new FilterList(Operator.MUST_PASS_ALL, rootList));
    }

    /**除了上面用到的
     * Htable 还有一些其他的有用方法
     * @throws IOException
     */
    @SuppressWarnings("unused")
    @Test public void htableOthers() throws IOException{
        HTable table = (HTable)pool.getTable("user_test_xuyang");
        byte [] row = Bytes.toBytes("row-13");
        //获取指定row的 数据所在的Region的信息 :名字, 编码后名字(Hadoop 中的
        // 路径名), startKey endkey等--->hri  ;还有该Region所在的主机的地址信息--->addr
        HRegionLocation hrl = table.getRegionLocation(row);
        HRegionInfo hri= hrl.getRegionInfo();
        HServerAddress addr = hrl.getServerAddress();

        //获取所有Region的信息
        Map<HRegionInfo,HServerAddress> regions = table.getRegionsInfo();

        //获取该表所在的所有Region的  startKey 和 endKey
        Pair<byte[][], byte[][]> startendKeys = table.getStartEndKeys();
        //下面的是通过上面的实现的
        table.getStartKeys();
        table.getEndKeys();

        //table.getRowOrBefore(row, family) 这个一般用不到  0.92时候 就要被废弃了
    }

    /**
     * 操作完成后, 清理下资源还是很有必要的,
     * 在系统的ServletContextListener
     */
    @AfterClass
    public static void after(){
        try {
            if(conf!=null) HConnectionManager.deleteConnection(conf, false);
            if(pool!=null)pool.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
        // 路径名), startKey endkey等--->hri  ;还有该Region所在的主机的地址信息--->addr
        HRegionLocation hrl = table.getRegionLocation(row);
        HRegionInfo hri= hrl.getRegionInfo();
        HServerAddress addr = hrl.getServerAddress();

        //获取所有Region的信息
        Map<HRegionInfo,HServerAddress> regions = table.getRegionsInfo();

        //获取该表所在的所有Region的  startKey 和 endKey
        Pair<byte[][], byte[][]> starten

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

Hexo一句话说明

hexo是一个基于Node.js的静态博客程序,可以方便的生成静态网页托管在github和Heroku上。作者是来自台湾的@tommy351。引用@tommy351的话,hexo:

快速、简单且功能强大的 Node.js 博客框架。
A fast, simple & powerful blog framework, powered by Node.js.

类似于jekyll、Octopress、Wordpress,我们可以用hexo创建自己的博客,托管到github或Heroku上,绑定自己的域名,用markdown写文章。

Hexo在github上搭建个人Blog

阅读以下推荐文章,可轻松完成。

推荐阅读

Markdown 语法说明 (简体中文版)[1]
Hexo在github上构建免费的Web应用[2]
hexo系列教程:(一)hexo介绍[3]
hexo系列教程:(二)搭建hexo博客[4]
hexo系列教程:(三)hexo博客的配置、使用[5]
hexo系列教程:(四)hexo博客的优化技巧[6]
hexo系列教程:(五)hexo博客的优化技巧续[7]
Generating SSH keys[8]
Hexo 使用记录[9]
hexo你的博客[10]
使用Cover主题,在Coding上搭建Hexo博客[11]
Hexo 中文版[12]
Hexo搭建Github静态博客[13]
使用Hexo搭建博客[14]
使用Hexo搭建个人博客(基于hexo3.0)[15]
Hexo官方主题landscape-plus优化[16]