使用Phoenix将SQL代码移植至HBase

1.前言html

HBase是云计算环境下最重要的NOSQL数据库,提供了基于Hadoop的数据存储、索引、查询,其最大的优势就是能够经过硬件的扩展从而几乎无限的扩展其存储和检索能力。可是HBase与传统的基于SQL语言的关系数据库不管从理念仍是使用方式上都相去甚远,以致于要将基于SQL的项目移植到HBase时每每须要重写整个项目。java

为了解决这个问题,不少开源项目提供了HBase的类SQL中间件,意即提供一种在HBase上使用的类SQL语言,使得程序员可以像使用关系数据库同样使用HBase,Apache Phoenix就是其中的一个优秀项目。linux

本文介绍了如何将基于传统关系数据库的程序经过Apache Phoenix移植到基于HBase的云计算平台上的方法,并详细讲述了该过程当中碰到的种种困难。主要内容包括:程序员

HBase及云计算环境的安装配置;
HBase的Java API编程;
Phoenix的安装配置与使用;
Squirrel的安装配置与使用;
使用Phoenix移植SQL代码至HBase;
Phoenix性能调优;
本文的读者应该是数据库系统项目的开发人员和维护人员,云计算项目开发人员,最好具备如下基本知识:sql

Linux系统使用常识;
Hadoop、Hbase、Zookeeper等云计算环境使用常识;
Java编程开发基础;
SQL语言基础;
Oracle、SQLServer或Mysql等关系数据库使用管理基础;shell

2.HBase及云计算环境的安装配置数据库

2.1环境配置apache

云计算环境一般安装在linux或者CentOS等类UNIX操做系统中,本文涉及的软件至少须要三个,即Hadoop、Hbase和Zookeeper,其版本号以下:编程

hadoop-2.3.0-cdh5.1.0
zookeeper-3.4.5-cdh5.1.0
hbase-0.98.1-cdh5.1.0
注意:本文使用了云时代的版本5.1.0,因为此类软件版本众多,互相之间的兼容性复杂,所以最好统一采用cdh的版本。系统配置以下图所示:
windows

系统一共六个节点,即Node1~Node6,hadoop安装在所有六个节点上,其中Node1和Node2是NameNode,其余是DataNode;ZooKeeper安装在Node四、Node5和Node6上,其端口使用默认的2181;Hbase安装在Node一、Node3~Node6上,其中Node1是HMaster,其余是HRegionServer。具体参数配置能够参考其余文档,此处不作详细描述。

注意:客户端必须经过ZooKeeper找到Hbase的入口。对于客户来讲,只须要知道ZooKeeper在哪儿;须要访问hbase时,客户端去找ZooKeeper,ZooKeeper再去查询HBase的HMaster和HRegionServer等信息,具体状况见《HBase实战》63页。

2.2HBase Shell使用

环境配置成功后,便可使用HBase Shell对HBase数据库进行操做,相似于Oracle提供的sqlplus。

登录任意一个安装了HBase的服务器,输入:

hbase shell
list
便可列出该hbase中存储的全部表格。
建立一个名为test的表格,它带有一个名为cf的列族,并使用list来查看表格是否被建立,而后插入一些数据:

hbase(main):003:0> create 'test', 'cf'
0 row(s) in 1.2200 seconds
hbase(main):003:0> list
test
1 row(s) in 0.0550 seconds
hbase(main):004:0> put 'test', 'row1', 'cf:a', 'value1'
0 row(s) in 0.0560 seconds
hbase(main):005:0> put 'test', 'row2', 'cf:b', 'value2'
0 row(s) in 0.0370 seconds
hbase(main):006:0> put 'test', 'row3', 'cf:c', 'value3'
0 row(s) in 0.0450 seconds
使用scan来查看test表格中的内容:

hbase(main):007:0> scan 'test'
ROW COLUMN+CELL
row1 column=cf:a, timestamp=1288380727188, value=value1
row2 column=cf:b, timestamp=1288380738440, value=value2
row3 column=cf:c, timestamp=1288380747365, value=value3
3 row(s) in 0.0590 seconds

获得表中的一行数据:

hbase(main):008:0> get 'test', 'row1'
COLUMN CELL
cf:a timestamp=1288380727188, value=value1
1 row(s) in 0.0400 seconds

disable和drop一个表格:

hbase(main):012:0> disable 'test'
0 row(s) in 1.0930 seconds
hbase(main):013:0> drop 'test'
0 row(s) in 0.0770 seconds

退出shell:

hbase(main):014:0> exit
其余更多具体的命令请参看HBase的手册或者在线帮助。

3.HBase Java API 编程

使用HBase的Java API进行开发须要掌握HBase的基本理念,推荐阅读《HBase实战》一书。

在进行开发的操做系统(例如Windows、Linux或者CentOS)中解压hbase-0.98.1-cdh5.1.0.tar.gz,获得开发所依赖的全部jar包,位于hbase-0.98.1-cdh5.1.0/lib目录中。

在开发环境(例如Eclipse、NetBean或者Intellij)中创建工程,导入hbase-0.98.1-cdh5.1.0\lib中的全部jar包。

3.1关于远程链接HBase

在给出源代码以前,先介绍一下远程链接HBase的问题。从Oracle时代过来的程序员,显然指望获得数据库服务器的ip、port和Service Name之类的信息。可是在链接HBase时,你须要的倒是一个或多个ZooKeeper服务器的ip(或者hostname)和port,由于只有它才知晓整个HBase集群的元数据。

显然,使用hostname比使用ip要显得习惯更好,由于它带来了更大的可移植性,所以费一点笔墨讲讲linux和windows的hostname设置。

在linux下,hostname经过修改/etc/hosts文件来完成,在集群的每台服务器上加入以下内容:

192.168.1.101 Node1
192.168.1.102 Node2
192.168.1.103 Node3
192.168.1.104 Node4
192.168.1.105 Node5
192.168.1.106 Node6
在各自的/etc/sysconfig/network文件中,将“HOSTNAME=”修改成“HOSTNAME=Node?”(将Node?替换为本服务器的hostname)。

在Windows下(仅测试过Win7 64),修改Windows/System32/drivers/etc/hosts文件,加入:

192.168.1.101 Node1
192.168.1.102 Node2
192.168.1.103 Node3
192.168.1.104 Node4
192.168.1.105 Node5
192.168.1.106 Node6
(不一样的windows平台hosts文件的位置可能不同,建议装一个everything,桌面搜索速度极快)。

其实多种方法均可以链接到ZooKeeper,例如ip加端口:

public static String hbase_svr_ip = "192.168.1.104,
192.168.1.105, 192.168.1.106";
public static String hbase_svr_port =
"2181";
或者hostname加端口:

public static String hbase_svr_hostname = "Node4,Node5,Node6";
public static String hbase_svr_port =
"2181";
或者将端口直接写在ip后:

public static String hbase_svr_ip = "192.168.1.104:2181,
192.168.1.105:2181, 192.168.1.106:2181";

或者将端口直接写在hostname后:

public static String hbase_svr_hostname = "Node4:2181,Node5:2181,Node6:2181";

或者仅使用一个ZooKeeper服务器:

public static String hbase_svr_hostname = "Node4:2181";

具体使用哪一种方法就看程序员本身的偏好,也存在某种方法在某些版本中可能没法链接的问题,本文中没有穷尽测试,但我的认为hostname加端口的方法可能比较稳妥。

3.2源代码

本篇给出了使用Java API操做HBase的源代码,注意要将这几行替换为实际的ZooKeeper服务器地址、hostname和端口号:

public static String hbase_svr_ip = "192.168.1.104,
192.168.1.105, 192.168.1.106";
public static String hbase_svr_port =
"2181";
public static String hbase_svr_hostname = "Node4,Node5,Node6";
代码功能包括:

远程链接Hbase数据库;
建立表;
扫描全部表;
插入数据;
扫描数据;
删除数据;
删除表。
package com.wxb;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HConnection;
import org.apache.hadoop.hbase.client.HConnectionManager;
import org.apache.hadoop.hbase.client.HTableInterface;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.util.Bytes;

/**

  • @author wxb hbase的基本操做方法
    */
    public class HBaseSample {
    public static String hbase_svr_ip = "192.168.1.104, 192.168.1.105, 192.168.1.106";
    public static String hbase_svr_port = "2181";
    public static String hbase_svr_hostname = "Node4,Node5,Node6";
    private HConnection connection = null;
    Configuration config = null;

    /**
    • 构造函数,构造一个HBaseSample对象,必须在最后调用close方法来关闭全部的链接,释放全部的资源
      */
      public HBaseSample() {
      config = HBaseConfiguration.create();
      config.set("hbase.zookeeper.quorum", hbase_svr_hostname);
      config.set("hbase.zookeeper.property.clientPort", hbase_svr_port);
      // System.out.println(config.get("hbase.zookeeper.quorum"));
      // System.out.println(config.get("hbase.zookeeper.property.clientPort"));

      try {
      connection = HConnectionManager.createConnection(config);
      } catch (IOException e) {
      e.printStackTrace();
      }
      }

    /**
    • 释放资源
      */
      public void close() {
      try {
      if (null != connection) {
      connection.close();
      }
      } catch (IOException e) {
      e.printStackTrace();
      }
      }
    /**
    • 建立表格
    • @param tableName
    • @param columnFarily
      */
      public void createTable(final String tableName, String columnFarily) {
      if (null != config) {
      System.out.println("begin create table...");
      HBaseAdmin admin = null;
      try {
      admin = new HBaseAdmin(config);
      if (admin.tableExists(tableName)) {
      System.out.println(tableName + " is already exist!");
      } else {
      HTableDescriptor tableDesc = new HTableDescriptor(tableName);
      tableDesc.addFamily(new HColumnDescriptor(columnFarily));
      admin.createTable(tableDesc);
      System.out.println(tableDesc.toString()
      + " has been created.");
      }
      admin.close();
      } catch (IOException e) {
      e.printStackTrace();
      }
      } else {
      System.out.println("hbase could not connected!");
      }
      }
    /**
    • 向指定表格中添加一行数据
    • @param table
    • @param key
    • @param family
    • @param col
    • @param dataIn
    • @return
      */
      public boolean addOneRecord(String table, String key, String family,
      String col, byte[] dataIn) {
      if (null != connection) {
      try {
      HTableInterface tb = connection.getTable(table);
      Put put = new Put(key.getBytes());
      put.add(family.getBytes(), col.getBytes(), dataIn);
      tb.put(put);
      System.out.println("put data key = " + key);
      return true;
      } catch (IOException e) {
      System.out.println("put data failed.");
      return false;
      }
      } else {
      System.out.println("hbase could not connected!");
      return false;
      }
      }
    /**
    • 获得hbase中全部的表
    • @return
      */
      public List getAllTables() {
      List tables = null;
      if (connection != null) {
      try {
      HTableDescriptor[] allTable = connection.listTables();
      if (allTable.length > 0)
      tables = new ArrayList ();
      for (HTableDescriptor hTableDescriptor : allTable) {
      tables.add(hTableDescriptor.getNameAsString());
      System.out.println(hTableDescriptor.getNameAsString());
      }
      } catch (IOException e) {
      e.printStackTrace();
      }
      } else {
      System.out.println("hbase could not connected!");
      }
      return tables;
      }

    public byte[] getValueWithKey(String tableName, String rowKey,
    String family, String qualifier) {
    byte[] rel = null;
    if (null != connection) {
    try {
    HTableInterface table = connection.getTable(tableName);
    Get get = new Get(rowKey.getBytes());
    get.addColumn(Bytes.toBytes(family), Bytes.toBytes(qualifier));
    Result result = table.get(get);
    if (!result.isEmpty()) {
    rel = result.getValue(Bytes.toBytes(family),
    Bytes.toBytes(qualifier));
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    } else {
    System.out.println("hbase could not connected!");
    }
    return rel;
    }

    /**
    • 从表中删除一行
    • @param tableName
    • @param rowKey
      */
      public void deleteWithKey(String tableName, String rowKey) {
      if (null != connection) {
      try {
      HTableInterface table = connection.getTable(tableName);
      Delete delete = new Delete(rowKey.getBytes());
      table.delete(delete);
      } catch (IOException e) {
      e.printStackTrace();
      }
      } else {
      System.out.println("hbase could not connected!");
      }
      }
    /**
    • 获得一个表中的全部元素
    • @param tableName
      */
      public void getAllData(String tableName) {
      if (null != connection) {
      try {
      HTableInterface table = connection.getTable(tableName);
      Scan scan = new Scan();
      ResultScanner rs = table.getScanner(scan);
      for (Result r : rs) {
      Cell[] cells = r.rawCells();
      System.out.println("This row have " + cells.length
      + " cells:");
      for (Cell cell : cells) {
      String row = Bytes.toString(CellUtil.cloneRow(cell));
      String family = Bytes.toString(CellUtil
      .cloneFamily(cell));
      String qualifier = Bytes.toString(CellUtil
      .cloneQualifier(cell));
      String value = Bytes
      .toString(CellUtil.cloneValue(cell));
      System.out.println(String.format("%s:%s:%s:%s", row,
      family, qualifier, value));
      }
      }
      } catch (IOException e) {
      e.printStackTrace();
      }
      } else {
      System.out.println("hbase could not connected!");
      }
      }

    public void deleteTable(String tableName) {
    if (null != config) {
    System.out.println("begin delete table...");
    HBaseAdmin admin = null;
    try {
    admin = new HBaseAdmin(config);
    if (!admin.tableExists(tableName)) {
    System.out.println(tableName + " is not exist!");
    } else {
    admin.disableTable(tableName);
    admin.deleteTable(tableName);
    System.out.println(tableName + " has been deleted.");
    }
    admin.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    } else {
    System.out.println("hbase could not connected!");
    }
    }

    /**
    • @param args
      */
      public static void main(String[] args) {
      HBaseSample sample = new HBaseSample();
      // 1.create table and insert data
      sample.createTable("student", "fam1");
      sample.addOneRecord("student", "id1", "fam1", "name", "Jack".getBytes());
      sample.addOneRecord("student", "id1", "fam1", "address",
      "HZ".getBytes());

      // 2.list table
      sample.getAllTables();

      // 3.getValue
      byte[] value = sample.getValueWithKey("student", "id1", "fam1",
      "address");
      System.out.println("value = " + Bytes.toString(value));

      // 4.addOneRecord and delete
      // sample.addOneRecord("student", "id2", "fam1", "name", "wxb".getBytes());
      // sample.addOneRecord("student", "id2", "fam1", "address",
      // "here".getBytes());
      // sample.deleteWithKey("student", "id2");

      // 5.scan table
      sample.getAllData("student");

      // 6.delete table
      // sample.deleteTable("student");

      sample.close();
      }
      }
  1. Phoenix的安装配置与使用

从上一章能够看出,HBase的基本理念和传统的关系数据库是大相径庭的,为了使得熟悉SQL的程序员可以快速使用HBase,使用Apache Phoenix是比较好的办法。它提供了一组相似于SQL的语法,以及序列、索引、函数等工具,使得将SQL代码移植至HBase成为可能。

4.1 Phoenix安装

同其余分布式软件同样,Phoenix的安装也是较为复杂的,且要密切关注其版本兼容性,不然极可能没法正常运行。例如Phoenix4.x版本都有兼容HBase0.98的版本,可是通过两天的测试才发现不一样的Phoenix版本对HBase0.98的小版本号的要求不一样。
因为本文使用的是HBase0.98.1,所以只能使用Phoenix4.1.0版本。若是使用的Phoenix版本和HBase版本不兼容,会出现第一次可以链接HBase,但之后都链接失败的现象。
Phoenix的具体安装步骤以下:
第一步:将phoenix-4.1.0-bin.tar.gz拷贝到Node1(HBase的HMaster)的某路径下,解压缩,拷贝hadoop2/phoenix-4.1.0-server-hadoop2.jar到HBase的lib目录下。
第二步:而后用scp(关于scp和ssh的设置请参考网上的其余文章,假设用户名为hadoop)拷贝到各个regionserver的HBase的lib目录下:

scp phoenix-4.1.0-server-hadoop2.jar hadoop@Node3:/home/hadoop/hbase-0.98.1-cdh5.1.0/lib/
phoenix-core-4.6.0-HBase-0.98.jar
scp phoenix-4.1.0-server-hadoop2.jar hadoop@Node4:/home/hadoop/hbase-0.98.1-cdh5.1.0/lib/
phoenix-core-4.6.0-HBase-0.98.jar
scp phoenix-4.1.0-server-hadoop2.jar hadoop@Node5:/home/hadoop/hbase-0.98.1-cdh5.1.0/lib/
phoenix-core-4.6.0-HBase-0.98.jar
scp phoenix-4.1.0-server-hadoop2.jar hadoop@Node6:/home/hadoop/hbase-0.98.1-cdh5.1.0/lib/
phoenix-core-4.6.0-HBase-0.98.jar
第三步:在HMaster上重启hbase(即Node1);
第四步:将phoenix-4.1.0-client-hadoop2.jar加入客户端的CLASSPATH变量路径中,修改用户的.bash_profile文件,同时将此文件拷贝到hbase的lib目录下。
第五步:测试使用phoenix,输入命令:

sqlline.py Node4:2181
注意:后面的参数是ZooKeeper的服务器和端口。
出现如下显示则说明链接成功。

[hadoop@iips25 hadoop2]$bin/sqlline.py Node1:2181
Setting property: [isolation, TRANSACTION_READ_COMMITTED]
issuing: !connect jdbc:phoenix:Node4 none none org.apache.phoenix.jdbc.PhoenixDriver
Connecting to jdbc:phoenix:Node4
16/06/21 08:04:24 WARN impl.MetricsConfig: Cannot locate configuration: tried hadoop-metrics2-phoenix.properties,hadoop-metrics2.properties
Connected to: Phoenix (version 4.1)
Driver: org.apache.phoenix.jdbc.PhoenixDriver (version 4.1)
Autocommit status: true
Transaction isolation: TRANSACTION_READ_COMMITTED
Building list of tables and columns for tab-completion (set fastconnect to true to skip)...
59/59 (100%) Done
Done
sqlline version 1.1.2
0: jdbc:phoenix:Node4>
查看数据库表:(注意,phoenix只能看到本身建立的表,不能看到HBase建立的表)

0: jdbc:phoenix:Node4> !tables
+------------+-------------+------------+------------+------------+------------+---------------------------+----------------+-------------+----------------+--------+
| TABLE_CAT | TABLE_SCHEM | TABLE_NAME | TABLE_TYPE | REMARKS | TYPE_NAME | SELF_REFERENCING_COL_NAME | REF_GENERATION | INDEX_STATE | IMMUTABLE_ROWS | SALT_B |
+------------+-------------+------------+------------+------------+------------+---------------------------+----------------+-------------+----------------+--------+
| null | SYSTEM | CATALOG | SYSTEM TABLE | null | null | null | null | null | false | null |
| null | SYSTEM | SEQUENCE | SYSTEM TABLE | null | null | null | null | null | false | null |
+------------+-------------+------------+------------+------------+------------+---------------------------+----------------+-------------+----------------+--------+
0: jdbc:phoenix:Node4>
建立表,并插入数据:

0: jdbc:phoenix:Node4> create table abc(a integer primary key, b integer) ;
No rows affected (1.133 seconds)
0: jdbc:phoenix:Node4> UPSERT INTO abc VALUES (1, 1);
1 row affected (0.064 seconds)
0: jdbc:phoenix:Node4> UPSERT INTO abc VALUES (2, 2);
1 row affected (0.009 seconds)
0: jdbc:phoenix:Node4> UPSERT INTO abc VALUES (3, 12);
1 row affected (0.009 seconds)
0: jdbc:phoenix:Node4> select * from abc;
+------------+------------+
| A | B |
+------------+------------+
| 1 | 1 |
| 2 | 2 |
| 3 | 12 |
+------------+------------+
3 rows selected (0.082 seconds)
0: jdbc:phoenix:Node4>
建立包含中文的表(注意中文要使用VARCHAR):

create table user ( id integer primary key, name VARCHAR);
upsert into user values ( 2, '测试员2');
upsert into user values ( 1, '测试员1');
select * from user;
+------------+------------+
| ID | NAME |
+------------+------------+
| 1 | 测试员1 |
| 2 | 测试员2 |
4.2 phoenix配置

在hbase集群每一个服务器的hbase-site.xml配置文件中,加入:


hbase.regionserver.wal.codec
org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec

这是在phoenix中创建索引的先决条件。若是不添加此设置,Phoenix依然能够正常使用,但不能创建索引。

4.3 phoenix语法简介

phoenix的语法可参考其官方网站,也可下载其“Grammar _ Apache Phoenix.html”网页。
访问Phoenix时,可使用其提供的sqlline.py命令,也可使用下一章介绍的数据库图形界面工具Squirrel,固然也能够经过Phoenix提供的Java API。

4.3.1. 建立表

注意:Phoenix中的表必须有主键,这一点和许多关系数据库不一样。由于主键是后续不少表操做的必备因素。

CREATE TABLE IF NOT EXISTS MYTABLE (ID INTEGER PRIMARY KEY, NAME VARCHAR, SEX VARCHAR, ADDRESS VARCHAR);
4.3.2. 删除表

DROP TABLE IF EXISTS MYTABLE;
4.3.3. 插入数据

UPSERT INTO MYTABLE VALUES (1, 'WXB', 'MALE', '010-22222222');
注意phoenix使用UPSERT而不是INSERT。

4.3.4. 删除数据

DELETE FROM MYTABLE WHERE ID = 1;
4.3.5. 查询数据

SELECT * FROM MYTABLE WHERE ID=1;
4.3.6. 修改数据

UPSERT INTO MYTABLE VALUES (1, 'WXB', 'MALE', '010-22222222');
能够看到,修改数据与插入数据同样,都是使用UPSERT语句,若此主键对应的行不存在,就插入,不然就修改。这也是为何Phoenix的表必须有主键的缘由之一。

4.3.7. 建立序列

Phoenix的序列与Oracle很像,也是先建立,而后调用next获得下一个值。也能够继续调用current value获得当前序列值,没有调用next时,不能使用current value。
建立一个序列:

CREATE SEQUENCE IF NOT EXISTS WXB_SEQ START WITH 1000 INCREMENT BY 1 MINVALUE 1000 MAXVALUE 999999999 CYCLE CACHE 30;
其含义基本上与Oracle相似。

4.3.8. 使用序列

序列只能在Select或者Upsert语句中使用,例如在Upsert中使用:

UPSERT INTO MYTABLE VALUES (NEXT VALUE FOR WXB_SEQ, 'WXB', 'MALE', '010-22222222');
读取序列的当前值时,采用这个语句:

SELECT CURRENT VALUE FOR WXB_SEQ DUALID FROM WXB_DUAL;
而后读取DUALID就可获得序列的当前值。
这里的WXB_DUAL是我本身建立的一个特殊表,用来模拟Oracle中的Dual表。

CREATE TABLE IF NOT EXISTS WXB_DUAL (DUALID INTEGER PRIMARY KEY );
UPSERT INTO WXB_DUAL VALUES (1);
4.3.9. 删除序列

DROP SEQUENCE IF EXISTS WXB_SEQ;

本章至此为止,详细的操做留待后续再讲。

  1. 安装SQuirrel

Squirrel是一个图形化的数据库工具,它能够将Phoenix以图形化的方式展现出来,它能够安装在windows或linux系统中。

5.1 安装步骤

第一步:
设置好JDK,JAVA_HOME,CLASSPATH等一系列的环境变量,注意不管是在windows仍是在linux下,都须要上面安装的hbase和phoenix的存放jar包的目录,并将其设置到CLASSPATH中。windows下的CLASSPATH以下:

%JAVA_HOME%\lib;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;D:\hbase-0.98.1-cdh5.1.0\lib;D:\phoenix-4.1.0-bin\hadoop2
linux的CLASSPATH以下:

export PHOENIX_HOME=/home/hadoop/phoenix-4.1.0-bin
export CLASSPATH=$PHOENIX_HOME/hadoop2/phoenix-4.1.0-client-hadoop2.jar:$HBASE_HOME/lib/:$CLASSPATH
export PATH=$PHOENIX_HOME/bin:$PATH

第二步:
下载解压squirrel-sql-snapshot-20160613_2107-standard.jar(最新版本的squirrel安装包),在命令行中运行java -jar squirrel-sql-snapshot-20160613_2107-standard.jar开始安装。
第三步:执行以下安装

  1. Remove prior phoenix-[oldversion]-client.jar from the lib directory of SQuirrel, copy phoenix-[newversion]-client.jar to the lib directory (newversion should be compatible with the version of the phoenix server jar used with your HBase installation)
  2. Start SQuirrel and add new driver to SQuirrel (Drivers -> New Driver)
  3. In Add Driver dialog box, set Name to Phoenix, and set the Example URL to jdbc:phoenix:localhost.
  4. Type “org.apache.phoenix.jdbc.PhoenixDriver” into the Class Name textbox and click OK to close this dialog.
  5. Switch to Alias tab and create the new Alias (Aliases -> New Aliases)
  6. In the dialog box, Name:Any name, Driver: Phoenix, User Name:Anything, Password:Anything
  7. Construct URL as follows: jdbc:phoenix:zookeeper quorum server. For example, to connect to a local HBase use: jdbc:phoenix:localhost
  8. Press Test (which should succeed if everything is setup correctly) and press OK to close.
  9. Now double click on your newly created Phoenix alias and click Connect. Now you are ready to run SQL queries against Phoenix.
    注意,咱们链接的URL是jdbc:phoenix:Node4,用户名和密码随意便可。链接成功后,以下:

5.2 使用

安装完毕后,就能够在Squirrel中执行各类phoenix支持的类SQL语句和观察数据了,例如在SQL栏中输入以下语句:

CREATE TABLE IF NOT EXISTS MYTABLE (ID INTEGER PRIMARY KEY, NAME VARCHAR, SEX VARCHAR, ADDRESS VARCHAR);

UPSERT INTO MYTABLE VALUES (1, 'WXB', 'MALE', '010-22222222');

UPSERT INTO MYTABLE VALUES (2, ‘LL’, 'MALE', '010-11111111');

SELECT * FROM MYTABLE;

结果以下:

使用Squirrel的好处在于能够方便的查看数据库中的各类对象,以及编辑和执行复杂的phoenix类sql脚本。

  1. 使用Phoenix移植SQL代码至HBase

Phoenix提供了彻底适配JDBC的API,程序员能够像操做关系数据库(例如Oracle)同样来使用JDBC来操做Phoenix,这也是Phoenix的最大的优点所在。惟一须要注意的是,提交的SQL语句必须符合Phoenix语法,虽然此语法很相似于SQL,但仍是有许多不一样之处。

6.1 Phoenix Java Coding

本章给出了一个最基本的Phoenix JDBC源代码实例,注意其中所引用的全部类几乎都来自于java.sql.*包,与Oracle惟一的不一样是其driver的字符串,该字符串等于前面链接Squirrel的链接字符串,你能够在Squirrel上测试driver字符串是否可以正确链接。driver字符串通常为jdbc:phoenix:ZooKeeper_hostname:port,例如jdbc:phoenix:Node4,Node5,Node6:2181。可是在端口为默认2181端口时,也能够省略端口号。
编码以前将phoenix-4.1.0-client-hadoop2.jar加入java项目的依赖Libraries,例子代码以下:

package com.wxb;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/**

  • @author wxb Phoenix的基本操做方法
  • */
    public class PhoenixSample {
    public static String hbase_svr_ip = "192.168.1.104, 192.168.1.105, 192.168.1.106";
    public static String hbase_svr_port = "2181";
    public static String hbase_svr_hostname = "Node4,Node5,Node6";

    /*
    • 全部几种方式的driver都可以经过测试: 1.Node4 2.Node4,Node5,Node6 3.Node4:2181
    • 4.Node4,Node5,Node6:2181 5.Node4:2181,Node5:2181,Node6:2181
    • 6.101.60.27.114
      */
      public static String driver = "jdbc:phoenix:" + hbase_svr_hostname;

    public static void createTable(String tableName) {
    System.out.println("create table " + tableName);
    Statement stmt = null;

    try {
         Connection con = DriverManager.getConnection(driver);
         stmt = con.createStatement();
    
         stmt.executeUpdate("create table  if not exists " + tableName
                 + " (mykey integer not null primary key, mycolumn varchar)");
         con.commit();
         con.close();
     } catch (SQLException e) {
         e.printStackTrace();
     }

    }

    public static void addRecord(String tableName, String values) {
    Statement stmt = null;

    try {
         Connection con = DriverManager.getConnection(driver);
         stmt = con.createStatement();
    
         stmt.executeUpdate("upsert into " + tableName + " values ("
                 + values + ")");
         con.commit();
         con.close();
     } catch (SQLException e) {
         e.printStackTrace();
     }

    }

    public static void deleteRecord(String tableName, String whereClause) {
    Statement stmt = null;

    try {
         Connection con = DriverManager.getConnection(driver);
         stmt = con.createStatement();
    
         stmt.executeUpdate("delete from " + tableName + " where "
                 + whereClause);
         con.commit();
         con.close();
     } catch (SQLException e) {
         e.printStackTrace();
     }

    }

    public static void createSequence(String seqName) {
    System.out.println("Create Sequence :" + seqName);
    Statement stmt = null;

    try {
         Connection con = DriverManager.getConnection(driver);
         stmt = con.createStatement();
    
         stmt.executeUpdate("CREATE SEQUENCE IF NOT EXISTS "
                 + seqName
                 + " START WITH 1000 INCREMENT BY 1 MINVALUE 1000 MAXVALUE 999999999 CYCLE CACHE 30");
         con.commit();
         con.close();
     } catch (SQLException e) {
         e.printStackTrace();
     }

    }

    public static void dropSequence(String seqName) {
    System.out.println("drop Sequence :" + seqName);
    Statement stmt = null;

    try {
         Connection con = DriverManager.getConnection(driver);
         stmt = con.createStatement();
    
         stmt.executeUpdate("DROP SEQUENCE IF EXISTS " + seqName);
         con.commit();
         con.close();
     } catch (SQLException e) {
         e.printStackTrace();
     }

    }

    public static void getAllData(String tableName) {

    System.out.println("Get all data from :" + tableName);
     ResultSet rset = null;
    
     try {
         Connection con = DriverManager.getConnection(driver);
         PreparedStatement statement = con.prepareStatement("select * from "
                 + tableName);
         rset = statement.executeQuery();
         while (rset.next()) {
             System.out.print(rset.getInt("mykey"));
             System.out.println(" " + rset.getString("mycolumn"));
         }
         statement.close();
         con.close();
     } catch (SQLException e) {
         e.printStackTrace();
     }

    }

    public static void dropTable(String tableName) {

    Statement stmt = null;
    
     try {
         Connection con = DriverManager.getConnection(driver);
         stmt = con.createStatement();
    
         stmt.executeUpdate("drop table  if  exists " + tableName);
         con.commit();
         con.close();
         System.out.println("drop table " + tableName);
     } catch (SQLException e) {
         e.printStackTrace();
     }

    }

    public static void main(String[] args) {
    createTable("wxb_test");
    createSequence("WXB_SEQ_ID");

    // 使用了Sequence
     addRecord("wxb_test", "NEXT VALUE FOR WXB_SEQ_ID,'wxb'");
     addRecord("wxb_test", "NEXT VALUE FOR WXB_SEQ_ID,'wjw'");
     addRecord("wxb_test", "NEXT VALUE FOR WXB_SEQ_ID,'wjl'");
    
     // deleteRecord("wxb_test", " mykey = 1 ");
     getAllData("wxb_test");
    
     // dropTable("wxb_test");

    // dropSequence("WXB_SEQ_ID");

    }
    }
    6.2 每一个表必须包含一个主键

在使用Phoenix时,创建的每一个表都必须包含一个主键,这与关系数据库不一样。并且每一个表的主键会自动被索引,这意味着在select语句的where子句中使用主键做为条件,会获得最快的查询速度。关于索引,在后续章节中再详细介绍。
个人建议是,为每一个表建立一个序列,并在插入数据时以序列的值做为主键的值。

6.3 JDBC链接池

Phoenix支持用户本身建立JDBC链接池,能够将基于JDBC链接池的代码复制过来,把Driver部分修改一番便可。

6.4 中文支持

涉及中文的字段可设置为VARCHAR类型,经测试没有问题。

6.5 CLOB和BLOB

CLOB和BLOB字段我都设置为VARCHAR类型,经测试存储400k字节的数据没有问题,更多的没有测试。

6.6 复杂的SQL语句

由于本文使用的Phoenix版本不是最新版,所以官网上给出的SQL语法不是彻底都可以支持,例以下面的语句就不能支持:

delete from wxb_senword where swid in (select swid from wxb_rela_sw_group where groupid=1)
所以对于一些复杂的SQL语句,须要先到官网上查询语法,而后在phoenix中进行测试,测试经过后才可以在程序中使用。
两个表的关联查询是可行的,语句以下:

SELECT d.swid,d.swname, d.userid, e.groupid FROM wxb_senword d JOIN wxb_rela_sw_group e ON e.swid = d.swid where e.groupid=1;

  1. Phoenix性能调优

7.1 代码移植流程

将基于SQL的java代码移植到Phoenix其实不难,以Oracle为例,基本流程以下:

将Oracle中的全部表在Phoenix中从新创建一次,没有主键的本身加一个主键(并创建对应的序列);
将Oracle中全部的序列、视图都在Phoenix中从新创建一次;
将程序中的每条SQL语句都翻译为Phoenix的SQL语句,并测试该语句是否可以正确运行,若不能,总能找到几条简单的语句进行替代。
7.2 Oracle和HBase的性能差别

移植完成后,通过一系列debug,程序总算可以正常运行了。可是性能问题会变得很是严重,这是关系数据库和HBase之间的设计思路和应用问题域之间的差别形成的。
Oracle的设计思路是尽量的快速对数据进行操做,可是随着表中记录数的不断增长,查询性能持续降低。要对Oracle进行硬件扩充会比较困难,并且会在单表一亿条左右时(没有通过本人验证)碰到性能瓶颈。Oracle的优点是在表中记录数很少(几百万之内,具体看服务器性能)时拥有极高的查询速度。
而HBase的优点是让单表能够存储几乎无限的记录,而且能够方便的扩充硬件,使得查询速度能够达到一个稳定的标准。可是其缺点在于表中数据很少时,查询速度相对较慢。经测试,Phoenix的表在记录数不多时(数十条),查询单条数据也须要0.2秒左右(服务器集群配置见前面的章节),而同时单服务器的Oracle查询这样的数据仅需30ms左右,相差接近十倍。

7.3 Phoenix索引性能测试

与Oracle相比,Phoenix在性能上还有一个特色就是在没有索引的状况下,查询性能降低很快。
例以下表:

CREATE TABLE IF NOT EXISTS WXB_WORD (ID INTEGER PRIMARY KEY, NAME VARCHAR, VALUE DOUBLE, HEAT INTEGER, FOCUSLEVEL INTEGER, USERID INTEGER);
不创建索引的状况下,在前面介绍的集群上进行查询性能测试,查询语句以下(确保单条命中):

SELECT * FROM WXB_WORD WHERE NAME=’XXX’;
50万条记录,平均单条查询时间为0.38秒;
100万条记录,平均单条查询时间为0.79秒;
500万条记录,平均单条查询时间为4.31秒;
然而在NAME字段上创建索引后,将表中数据增长到1亿条,平均单条查询时间为0.164秒,可见索引对Phoenix性能的提高做用是无可替代的。

7.4 Phoenix索引简介

Phoenix中的索引被称之为Secondary Indexing(二级索引),这是为了和HBase主键上的索引区分开。在HBase中,每一个表有且仅有一个主键的索引,该索引按照字典序进行排序;全部不基于主键的查询都会致使全表扫描,效率很是低下。在Phoenix中,能够对表中的任何一个字段或者几个字段创建二级索引,该索引其实是一个独立的表,表中包含了被索引的列以及创建索引时包含的列(在索引的include语句中包含的列)。当用户对表进行查询时,会首先对索引进行查询,若可以获得所有的结果,则会直接返回,不然就到原表中进行查询。
注意,Phoenix的每一个表均可以创建多个索引,索引和原表之间的同步由Phoenix保证。可是,索引越多,写入效率越低。
Phoenix支持两种类型的索引:可变索引(mutable indexing)和不可变索引(immutable indexing)。在表中数据须要变化时,使用可变索引;当应用场景为“一次写入,只会追加,永不改变”时使用不可变索引。本文中只使用了可变索引。

7.5 创建索引的方法与语句

在创建索引以前,再次检查Phoenix的配置,在HBase集群的每一个服务器的hbase-site.xml配置文件中,加入:


hbase.regionserver.wal.codec
org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec

例如:在WXB_WORD表上对NAME字段创建DESC索引,该索引还包含了VALUE字段的值(注意,Phoenix是大小写不敏感的)。

create index if not exists idx_wxb_word on wxb_word (name desc) include (value) ;
那么这种语句就查询得特别快:

select name,value from wxb_word where name='AHNHLYPKGYAR_59999';
可是若是查询语句中还须要知道其余字段的值,例如:

select name,value,userid from wxb_word where name='AHNHLYPKGYAR_59999';
那么,就和没有索引差很少,由于该索引中没有包含userid这个字段。
另外须要注意的是:主键不须要索引,查询也很是快,这是由HBASE的特性保证的。
删除索引语句:

drop index if exists idx_wxb_word on wxb_word;

  1. 总结

使用Phoenix将SQL代码移植到HBase应注意如下几个问题。

第一,应用场景是否合适?是否须要在单表中存储几乎无限的数据,并保证必定的查询性能?在数据量较少的情景下,Phoenix反而比Oracle的性能差。若要追求最高的性能,能够考虑同时使用关系数据库和HBase,并本身保证这部分数据的同步。 第二,Phoenix、HBase、Hadoop、ZooKeeper的版本兼容问题。在大部分状况下,开发人员并不能决定HBase、Hadoop和ZooKeeper的版本,所以只能寻找合适的Phoenix版原本适配它们,这将致使你不能使用最新的Phoenix版本。如同本文中写的同样,这种状况会致使一些Phoenix SQL语句的特性得不到支持。 第三,注意Phoenix的每一个表必须包含一个主键(其实就是HBase的Primary rowkey),且该主键自带索引,合理设计这个主键可以带来性能上的提高和查询的便利。做为从SQL时代过来的程序员,抛弃节约空间的想法;在大数据时代,就是尽量的用空间换时间。举个例子,你甚至能够将全部字段以必定的顺序和分隔符所有堆到主键上。 第四,移植代码时,将全部SQL语句一一翻译为对应的Phoenix语句便可。注意参考Phoenix主页上的语法介绍,并一一进行测试。Phoenix对JDBC的支持很好,诸如链接池一类的特性能够原封不动的照搬。但若原来的程序使用了针对SQL语句的中间件之类的技术,请恕我也不知如何处理。 第五,必定要对Phoenix的表创建二级索引,索引中尽量包含全部须要查询的字段。索引会致使数据插入速度变慢,但会带来巨大的性能提高。

相关文章
相关标签/搜索