NoSQL 數據庫之 HBase

HBase 簡介

Hadoop Database,是一個高可靠性、高性能、面向列、可伸縮、實時讀寫的分佈式數據庫

作用:主要用來存儲非結構化和半結構化的鬆散數據(列存 NoSQL 數據庫)

利用 Hadoop HDFS 作爲其文件存儲系統,

利用 Hadoop MapReduce 來處理 HBase 中的海量數據,

利用 Zookeeper 作爲其分佈式協同服務

  • 非關係型數據庫知識面擴展

    • Cassandra hbase mongodb
    • Couchdb,文件存儲數據庫
    • Neo4j非關係型圖數據庫
  • HBase 官網地址:http://hbase.apache.org/

Hadoop 生態系統

MapReduce 計算的數據來源:

  1. HDFS 或者其他文件系統 2.數據庫(既可以是關係型數據也可以非關係型)

Hive 和 HBase 的區別:

Hive 是數據倉庫,HBase 是數據庫。Hive 是用 SQL 方式處理數據,底層使用 MapReduce 計算框架

使用 MapReduce 注意:

  1. map 生成小文件 2. 數據傾斜

HBase 數據模型

  • ROW KEY(ROW KEY 設計很重要)

    決定一行數據,按照字典順序排序的。Row key只能存儲 64k 的字節數據

  • Column Family 列族 & qualifier列

    1. HBase 表中的每個列都歸屬於某個列族,列族必須作爲表模式(schema)定義的一部分預先給出。

      如 create ‘test’, ‘course’;

    2. 列名以列族作爲前綴,每個“列族”都可以有多個列成員(column);

      如course:math, course:english, 新的列族成員(列)可以隨後按需、動態加入;

    3. 權限控制、存儲以及調優都是在列族層面進行的;

    4. HBase 把同一列族裏面的數據存儲在同一目錄下,由幾個文件保存。

  • Cell 單元格

    由行和列的座標交叉決定;

    單元格是有版本的;

    單元格的內容是未解析的字節數組

    {row key, column( =<family> +<qualifier>), version} 唯一確定的單元。

    cell中的數據是沒有類型的,全部是字節碼形式存貯

  • Timestamp 時間戳

    在 HBase 每個 cell 存儲單元對同一份數據有多個版本,根據唯一的時間戳來區分每個版本之間的差異,不同版本的數據按照時間倒序排序,最新的數據版本排在最前面。

    時間戳的類型是 64位整型。

    時間戳可以由 HBase (在數據寫入時自動)賦值,此時時間戳是精確到毫秒的當前系統時間。

    時間戳也可以由客戶顯式賦值,如果應用程序要避免數據版本衝突,就必須自己生成具有唯一性的時間戳。

  • HLog(WAL log)

    HLog 文件就是一個普通的 Hadoop Sequence File,Sequence File 的 Key 是 HLogKey 對象,HLogKey 中記錄了寫入數據的歸屬信息,除了 table 和 region 名字外,同時還包括 sequence number 和 timestamp,timestamp 是” 寫入時間”,sequence number 的起始值爲0,或者是最近一次存入文件系統中 sequence number。
    HLog SequeceFile 的 Value 是 HBase 的 KeyValue 對象,即對應 HFile 中的 KeyValue。

注:region:範圍、地區

HBase 架構

HBase 架構圖

HBase 架構中各角色作用

  • Client
    包含訪問 HBase 的接口並維護 cache 來加快對 HBase 的訪問

  • Zookeeper(不僅可以做集羣的高可用)
    保證任何時候,集羣中只有一個 master
    存貯所有 Region 的尋址入口。
    實時監控 Region server 的上線和下線信息。並實時通知 Master
    存儲 HBase 的 schema 和 table 元數據

  • Master
    爲 Region server 分配 region
    負責 Region server 的負載均衡
    發現失效的 Region server 並重新分配其上的 region
    管理用戶對 table 的增刪改操作

  • RegionServer
    Region server 維護 region,處理對這些 region 的 IO 請求
    Region server 負責切分在運行過程中變得過大的 region

  • Region
    HBase 自動把表水平劃分成多個區域(region),每個 region 會保存一個表裏面某段連續的數據(按字典序)

    每個表一開始只有一個 region,隨着數據不斷插入表,region 不斷增大,當增大到一個閥值的時候,region就會等分會兩個新的 region(裂變)

    當 table 中的行不斷增多,就會有越來越多的 region。這樣一張完整的表被保存在多個 Regionserver 上。

  • Memstore & storefile
    一個 region 由多個 store 組成,一個 store 對應一個CF(列族)

    store 包括位於內存中的 memstore 和位於磁盤的 storefile 。

    寫操作先寫入memstore,當 memstore 中的數據達到某個閾值,hregionserver 會啓動 flashcache 進程寫

    入 storefile,每次寫入形成單獨的一個 storefile。

    當 storefile 文件的數量增長到一定閾值後,系統會進行合併(minor、major compaction),在合併過程

    中會進行版本合併和刪除工作(majar),形成更大的 storefile。

    當一個 region 所有 storefile的大小和數量超過一定閾值後,會把當前的 region 分割爲兩個,並由 hmaster

    分配到相應的 regionserver 服務器,實現負載均衡。

    客戶端檢索數據,先在 memstore 找,找不到再找 storefile(先找 cache 後找磁盤)

  • Region補充:

    HRegion 是 HBase 中分佈式存儲和負載均衡的最小單元。

    最小單元就表示不同的 HRegion 可以分佈在不同的 HRegion server上。

    HRegion 由一個或者多個 Store 組成,每個 store 保存一個 columns family。

    每個 Strore 又由一個 memStore 和 0 至多個 StoreFile 組成。

    如圖:StoreFile 以 HFile 格式保存在 HDFS上。

HBase 環境搭建

HBase 官方文檔地址:http://hbase.apache.org/book.html#quickstart

HBase 僞分佈式搭建

  • 系統環境

    JDK 1.7,並配置 JAVA_HOME 環境變量

  • HBase 安裝步驟

    # 上傳 HBase 壓縮包文件,並解壓到指定目錄, 通過 tar 命令中 -C 指定解壓到的目錄
    tar xf hbase-0.98.12.1-hadoop2-bin.tar.gz  -C  /opt/sxt
    
    # 配置 HBase 環境變量
    vi /etc/profile
    # 增加的配置內容
    export HBASE_HOME=/opt/sxt/hbase-0.98.12.1-hadoop2
    export PATH=$PATH:$JAVA_HOME/bin:$HBASE_HOME/bin
    # 環境變量生效
    . /etc/profile
    
    # 進入 $HBSE_HOME/conf 下
    # 修改 hbase-env.sh
    export JAVA_HOME=/usr/java/jdk1.7.0_67
    
    # 修改 hbase-site.xml 
    <property>
    <name>hbase.rootdir</name>
    <value>file:///home/testuser/hbase</value>
    </property>
    <property>
    <name>hbase.zookeeper.property.dataDir</name>
    <value>/home/testuser/zookeeper</value>
    </property>
    <property>
    <name>hbase.unsafe.stream.capability.enforce</name>
    <value>false</value>
    </property>
    
    # 啓動
    start-hbase.sh
    # 訪問 HBase ,端口號爲 60010
    http://192.168.170.105:60010
    # 
    hbase shell
    #help 查看 hbase 使用
    #list  列出所欲當前數據表
    #scan  列出當前數據表中記錄數
    #create 創建表
    #put    增加記錄
    #delete  刪除記錄
    
  • HBase 操作

補充: list 顯示當前 HBase 中所有表

​ flush 將 inmemstore 中數據寫入 storefile

HBase 完全分佈式搭建

  • 節點分佈

    NN DN ZK HMaster Backup-HMaster RegionServer
    node01 * *
    node02 * * *
    node03 * * *
    node04 * * *
    node05 *
  • 系統設置

    1. 集羣間網絡通信

    2. 時間同步

      # 安裝 ntp 服務
      yum install -y ntp
      # 時間服務器
      ntpdate ntp1.aliyun.com
      
    3. JDK 環境

    4. 免祕鑰登錄

      ssh-keygen
      ssh-copy-id -i /root/.ssh/id_rsa.pub host(需要免密鑰登錄的服務器地址)
      eg: ssh-copy-id -i .ssh/id_rsa.pub  node02
      
    5. hadoop 集羣啓動

      start-dfs.sh
      
  • HBase 搭建

    1. 上傳解壓

    2. 修改配置文件

      # node 節點上------------------------------
      # 進入 $HBASE_HOME/conf 目錄下
      # 編輯 vi  hbase-env.sh ,配置JDk 、關閉 zk 
      export JAVA_HOME=/usr/java/jdk1.7.0_67
      export HBASE_MANAGES_ZK=false
      
      # 編輯 vi hbase-site.xml ,增加內容爲 
      <property>
        <name>hbase.cluster.distributed</name>
        <value>true</value>
      </property>
      <property>
        <name>hbase.rootdir</name>
        <value>hdfs://mycluster:8020/hbase</value>
      </property>
      <property>
        <name>hbase.zookeeper.quorum</name>
        <value>node02,node03,node04</value>
      </property>
      
      # 編輯 vi regionservers,增加內容爲 
      node02
      node03
      node04
      # 編輯 vi backup-masters,增加內容爲 
      node05
      # 拷貝 $HADOOP_HOME/etc/hadoop/hdfs-size.xml 到 $HBASE_HOME/conf 目錄下(重要)
      
      # 分發到 node02、node03、node04、node05
      
      # 配置 node02、node03、node04、node05 的 hbase 環境變量
      
    3. 啓動

      start-hbase.sh
      

HBase-API

重點 : rowkey 設計

Demo

  1. 創建 hbase-demo 普通 java 項目,並導入 hadoop 和 hbase 相關依賴 jar 包,併發布到類路徑上

  2. 創建 HBaseDemo

public class HbaseDemo {

	HBaseAdmin admin;
	HTable htable;
	Configuration conf;
	String TN = "test";

	@Before
	public void init() throws Exception {
		conf = new Configuration();
		conf.set("hbase.zookeeper.quorum", "node02,node03,node04");
		admin = new HBaseAdmin(conf);
		htable = new HTable(conf, TN.getBytes());
	}
    
    	@After
	public void close() throws Exception {
		if (admin != null) {
			admin.close();
		}
    }
}
  • 創建表
@Test
public void createTable() throws Exception {
    HTableDescriptor table = new HTableDescriptor(TableName.valueOf(TN.getBytes()));
    HColumnDescriptor column = new HColumnDescriptor("cf");
    table.addFamily(column);
    if (admin.tableExists(TN.getBytes())) {
        admin.disableTable(TN.getBytes());
        admin.deleteTable(TN.getBytes());
    }
    admin.createTable(table);
}

  • 存放數據
public void put() throws Exception {
		String rowkey = "r124";
		Put put = new Put(rowkey.getBytes());
		put.add("cf".getBytes(), "name".getBytes(), "xiaoli".getBytes());
		put.add("cf".getBytes(), "age".getBytes(), "32".getBytes());
		put.add("cf".getBytes(), "sex".getBytes(), "female".getBytes());
		htable.put(put);
}
  • 獲取數據
@Test
public void showTable() throws Exception {
    Get get = new Get("r123".getBytes());
    // 必須加
    get.addColumn("cf".getBytes(), "name".getBytes());
    get.addColumn("cf".getBytes(), "age".getBytes());
    Result result = htable.get(get);
    Cell name = result.getColumnLatestCell("cf".getBytes(), "name".getBytes());
    Cell age = result.getColumnLatestCell("cf".getBytes(), "age".getBytes());
    // System.out.println(new String(name.getValue()));
    // System.out.println(new String(age.getValue()));
    System.out.print(new String(CellUtil.cloneValue(name)));
    System.out.print(new String(CellUtil.cloneValue(age)));
}

  • 條件查詢
	
	/**
	 * 生成電話號碼
	 * @param prefix
	 * @return
	 */
	Random r = new Random();
	private String generatePhoneNumber(String prefix) {
		String pNum = prefix + String.format("%08d", r.nextInt(99999999));
		return pNum;
	}

	/**
	 *  獲取隨機時間
	 */
	DateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
	public String getDate(String year) {
		// 月 天 小時 分鐘 秒
		Object[] args = { r.nextInt(12)+1, r.nextInt(31)+1, r.nextInt(24), r.nextInt(60), r.nextInt(60) };
		String dateStr = year + String.format("%02d%02d%02d%02d%02d", args);
		return dateStr;
	}
	
	/**
	 *  生成 10個人通話記錄
	 *  // phoneNum_(max-timestamp)  
	 */
	@Test
	public void addPhoneRecord() throws Exception{
		List<Put> puts = new ArrayList<>();
		for(int i = 0;i < 10;i++){
			String phoneNum = generatePhoneNumber("189");
			for(int j = 0;j < 100;j++){
				String dnum = generatePhoneNumber("138");
				String length = r.nextInt(99)+""; // 通話時長
				String type = r.nextInt(2)+""; // 1 是主叫,2 是被叫
				String dataStr  = getDate("2019");  // 通話開始時間
				String rowkey = phoneNum+"_"+(Long.MAX_VALUE-sdf.parse(dataStr).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(),"dataStr".getBytes(),dataStr.getBytes());
				puts.add(put);
			}
		}
		htable.put(puts);
	}

	/**
	 * 查找通話記錄 ,指定電話的在 1月份 4 月份之間的通話記錄
	 */
	@Test
	public void scan() throws Exception {
		String phoneNum = "18985484208";                            //20190022203207
		String startRow = phoneNum+"_"+(Long.MAX_VALUE-sdf.parse("20190401000000").getTime());
		String stopRow =  phoneNum+"_"+(Long.MAX_VALUE-sdf.parse("20190101000000").getTime());
		Scan scan = new Scan();
		scan.setStartRow(startRow.getBytes());
		scan.setStopRow(stopRow.getBytes());
		ResultScanner scanner = htable.getScanner(scan);
		int count = 0;
		for (Result rs : scanner) {
			System.out.print(new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "dnum".getBytes()))));
			System.out.print("\t"+new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "length".getBytes()))));
			System.out.print("\t"+new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "type".getBytes()))));
			System.out.println("\t"+new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "dataStr".getBytes()))));
			count ++;
		}
		System.out.println("count:"+count);
	}

	/**
	 * 查找指定電話主叫的通話記錄
	 * @throws Exception
	 */
	@Test
	public void scan2() throws Exception{
		FilterList list = new FilterList(FilterList.Operator.MUST_PASS_ALL);
		PrefixFilter filter1 = new PrefixFilter("18985484208".getBytes());
		SingleColumnValueFilter filter2 = new SingleColumnValueFilter("cf".getBytes(), "type".getBytes(),
				CompareOp.EQUAL, "1".getBytes());
		list.addFilter(filter1);
		list.addFilter(filter2);
		Scan scan = new Scan();
		scan.setFilter(list);
		ResultScanner scanner = htable.getScanner(scan);
		int count = 0;
		for (Result rs : scanner) {
			System.out.print(new String(rs.getRow()));
			System.out.print("\t"+new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "dnum".getBytes()))));
			System.out.print("\t"+new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "length".getBytes()))));
			System.out.print("\t"+new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "type".getBytes()))));
			System.out.println("\t"+new String(CellUtil.cloneValue(rs.getColumnLatestCell("cf".getBytes(), "dataStr".getBytes()))));
			count ++;
		}
		System.out.println("count:"+count);
}
	

工具類

public class HBaseDAOImp {

	HConnection hTablePool = null;
	static Configuration conf = null;

	public HBaseDAOImp() {
		conf = new Configuration();
		String zk_list = "node01,node02,node03";
		conf.set("hbase.zookeeper.quorum", zk_list);
		try {
			hTablePool = HConnectionManager.createConnection(conf);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void save(Put put, String tableName) {
		HTableInterface table = null;
		try {
			table = hTablePool.getTable(tableName);
			table.put(put);

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				table.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 插入一個cell
	 * 
	 * @param tableName
	 * @param rowKey
	 * @param family
	 * @param quailifer
	 * @param value
	 */
	public void insert(String tableName, String rowKey, String family, String quailifer, String value) {
		// TODO Auto-generated method stub
		HTableInterface table = null;
		try {
			table = hTablePool.getTable(tableName);
			Put put = new Put(rowKey.getBytes());
			put.add(family.getBytes(), quailifer.getBytes(), value.getBytes());
			table.put(put);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				table.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 在一個列族下插入多個單元格
	 * 
	 * @param tableName
	 * @param rowKey
	 * @param family
	 * @param quailifer
	 * @param value
	 */
	public void insert(String tableName, String rowKey, String family, String quailifer[], String value[]) {
		HTableInterface table = null;
		try {
			table = hTablePool.getTable(tableName);
			Put put = new Put(rowKey.getBytes());
			// 批量添加
			for (int i = 0; i < quailifer.length; i++) {
				String col = quailifer[i];
				String val = value[i];
				put.add(family.getBytes(), col.getBytes(), val.getBytes());
			}
			table.put(put);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				table.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public void save(List<Put> Put, String tableName) {
		HTableInterface table = null;
		try {
			table = hTablePool.getTable(tableName);
			table.put(Put);
		} catch (Exception e) {
		} finally {
			try {
				table.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	public Result getOneRow(String tableName, String rowKey) {
		HTableInterface table = null;
		Result rsResult = null;
		try {
			table = hTablePool.getTable(tableName);
			Get get = new Get(rowKey.getBytes());
			rsResult = table.get(get);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				table.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return rsResult;
	}

	/**
	 * 最常用的方法,優化查詢 查詢一行數據,
	 * 
	 * @param tableName
	 * @param rowKey
	 * @param cols
	 * @return
	 */
	public Result getOneRowAndMultiColumn(String tableName, String rowKey, String[] cols) {
		HTableInterface table = null;
		Result rsResult = null;
		try {
			table = hTablePool.getTable(tableName);
			Get get = new Get(rowKey.getBytes());
			for (int i = 0; i < cols.length; i++) {
				get.addColumn("cf".getBytes(), cols[i].getBytes());
			}
			rsResult = table.get(get);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				table.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return rsResult;
	}

	public List<Result> getRows(String tableName, String rowKeyLike) {
		HTableInterface table = null;
		List<Result> list = null;
		try {
			FilterList fl = new FilterList(FilterList.Operator.MUST_PASS_ALL);
			table = hTablePool.getTable(tableName);
			PrefixFilter filter = new PrefixFilter(rowKeyLike.getBytes());
			SingleColumnValueFilter filter1 = new SingleColumnValueFilter("order".getBytes(), "order_type".getBytes(),
					CompareOp.EQUAL, Bytes.toBytes("1"));
			fl.addFilter(filter);
			fl.addFilter(filter1);
			Scan scan = new Scan();
			scan.setFilter(fl);
			ResultScanner scanner = table.getScanner(scan);
			list = new ArrayList<Result>();
			for (Result rs : scanner) {
				list.add(rs);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				table.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return list;
	}

	public List<Result> getRows(String tableName, String rowKeyLike, String cols[]) {
		// TODO Auto-generated method stub
		HTableInterface table = null;
		List<Result> list = null;
		try {
			table = hTablePool.getTable(tableName);
			PrefixFilter filter = new PrefixFilter(rowKeyLike.getBytes());

			Scan scan = new Scan();
			for (int i = 0; i < cols.length; i++) {
				scan.addColumn("cf".getBytes(), cols[i].getBytes());
			}
			scan.setFilter(filter);
			ResultScanner scanner = table.getScanner(scan);
			list = new ArrayList<Result>();
			for (Result rs : scanner) {
				list.add(rs);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				table.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return list;
	}

	public List<Result> getRowsByOneKey(String tableName, String rowKeyLike, String cols[]) {
		// TODO Auto-generated method stub
		HTableInterface table = null;
		List<Result> list = null;
		try {
			table = hTablePool.getTable(tableName);
			PrefixFilter filter = new PrefixFilter(rowKeyLike.getBytes());

			Scan scan = new Scan();
			for (int i = 0; i < cols.length; i++) {
				scan.addColumn("cf".getBytes(), cols[i].getBytes());
			}
			scan.setFilter(filter);
			ResultScanner scanner = table.getScanner(scan);
			list = new ArrayList<Result>();
			for (Result rs : scanner) {
				list.add(rs);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				table.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return list;
	}

	/**
	 * 範圍查詢
	 * 
	 * @param tableName
	 * @param startRow
	 * @param stopRow
	 * @return
	 */
	public List<Result> getRows(String tableName, String startRow, String stopRow) {
		HTableInterface table = null;
		List<Result> list = null;
		try {
			table = hTablePool.getTable(tableName);
			Scan scan = new Scan();
			scan.setStartRow(startRow.getBytes());
			scan.setStopRow(stopRow.getBytes());
			ResultScanner scanner = table.getScanner(scan);
			list = new ArrayList<Result>();
			for (Result rsResult : scanner) {
				list.add(rsResult);
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				table.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return list;
	}

	public void deleteRecords(String tableName, String rowKeyLike) {
		HTableInterface table = null;
		try {
			table = hTablePool.getTable(tableName);
			PrefixFilter filter = new PrefixFilter(rowKeyLike.getBytes());
			Scan scan = new Scan();
			scan.setFilter(filter);
			ResultScanner scanner = table.getScanner(scan);
			List<Delete> list = new ArrayList<Delete>();
			for (Result rs : scanner) {
				Delete del = new Delete(rs.getRow());
				list.add(del);
			}
			table.delete(list);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				table.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	public void deleteCell(String tableName, String rowkey, String cf, String column) {
		HTableInterface table = null;
		try {
			table = hTablePool.getTable(tableName);
			Delete del = new Delete(rowkey.getBytes());
			del.deleteColumn(cf.getBytes(), column.getBytes());
			table.delete(del);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				table.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	public void createTable(String tableName, String[] columnFamilys) {
		try {
			// admin 對象
			HBaseAdmin admin = new HBaseAdmin(conf);
			if (admin.tableExists(tableName)) {
				System.err.println("此表,已存在!");
			} else {
				HTableDescriptor tableDesc = new HTableDescriptor(TableName.valueOf(tableName));

				for (String columnFamily : columnFamilys) {
					tableDesc.addFamily(new HColumnDescriptor(columnFamily));
				}

				admin.createTable(tableDesc);
				System.err.println("建表成功!");

			}
			admin.close();// 關閉釋放資源
		} catch (MasterNotRunningException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ZooKeeperConnectionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	/**
	 * 刪除一個表
	 * 
	 * @param tableName
	 *            刪除的表名
	 */
	public void deleteTable(String tableName) {
		try {
			HBaseAdmin admin = new HBaseAdmin(conf);
			if (admin.tableExists(tableName)) {
				admin.disableTable(tableName);// 禁用表
				admin.deleteTable(tableName);// 刪除表
				System.err.println("刪除表成功!");
			} else {
				System.err.println("刪除的表不存在!");
			}
			admin.close();
		} catch (MasterNotRunningException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ZooKeeperConnectionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * 查詢表中所有行
	 * 
	 * @param tablename
	 */
	public void scaner(String tablename) {
		try {
			HTable table = new HTable(conf, tablename);
			Scan s = new Scan();
			// s.addColumn(family, qualifier)
			// s.addColumn(family, qualifier)
			ResultScanner rs = table.getScanner(s);
			for (Result r : rs) {

				for (Cell cell : r.rawCells()) {
					System.out.println("RowName:" + new String(CellUtil.cloneRow(cell)) + " ");
					System.out.println("Timetamp:" + cell.getTimestamp() + " ");
					System.out.println("column Family:" + new String(CellUtil.cloneFamily(cell)) + " ");
					System.out.println("row Name:" + new String(CellUtil.cloneQualifier(cell)) + " ");
					System.out.println("value:" + new String(CellUtil.cloneValue(cell)) + " ");
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void scanerByColumn(String tablename) {

		try {
			HTable table = new HTable(conf, tablename);
			Scan s = new Scan();
			s.addColumn("cf".getBytes(), "201504052237".getBytes());
			s.addColumn("cf".getBytes(), "201504052237".getBytes());
			ResultScanner rs = table.getScanner(s);
			for (Result r : rs) {

				for (Cell cell : r.rawCells()) {
					System.out.println("RowName:" + new String(CellUtil.cloneRow(cell)) + " ");
					System.out.println("Timetamp:" + cell.getTimestamp() + " ");
					System.out.println("column Family:" + new String(CellUtil.cloneFamily(cell)) + " ");
					System.out.println("row Name:" + new String(CellUtil.cloneQualifier(cell)) + " ");
					System.out.println("value:" + new String(CellUtil.cloneValue(cell)) + " ");
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {

		// 創建表
		// String tableName="test";
		// String cfs[] = {"cf"};
		// dao.createTable(tableName,cfs);

		// 存入一條數據
		// Put put = new Put("bjsxt".getBytes());
		// put.add("cf".getBytes(), "name".getBytes(), "cai10".getBytes()) ;
		// dao.save(put, "test") ;

		// 插入多列數據
		// Put put = new Put("bjsxt".getBytes());
		// List<Put> list = new ArrayList<Put>();
		// put.add("cf".getBytes(), "addr".getBytes(), "shanghai1".getBytes()) ;
		// put.add("cf".getBytes(), "age".getBytes(), "30".getBytes()) ;
		// put.add("cf".getBytes(), "tel".getBytes(), "13889891818".getBytes())
		// ;
		// list.add(put) ;
		// dao.save(list, "test");

		// 插入單行數據
		// dao.insert("test", "testrow", "cf", "age", "35") ;
		// dao.insert("test", "testrow", "cf", "cardid", "12312312335") ;
		// dao.insert("test", "testrow", "cf", "tel", "13512312345") ;

		// List<Result> list = dao.getRows("test", "testrow",new
		// String[]{"age"}) ;
		// for(Result rs : list)
		// {
		// for(Cell cell:rs.rawCells()){
		// System.out.println("RowName:"+new String(CellUtil.cloneRow(cell))+"
		// ");
		// System.out.println("Timetamp:"+cell.getTimestamp()+" ");
		// System.out.println("column Family:"+new
		// String(CellUtil.cloneFamily(cell))+" ");
		// System.out.println("row Name:"+new
		// String(CellUtil.cloneQualifier(cell))+" ");
		// System.out.println("value:"+new String(CellUtil.cloneValue(cell))+"
		// ");
		// }
		// }

		// Result rs = dao.getOneRow("test", "testrow");
		// System.out.println(new String(rs.getValue("cf".getBytes(),
		// "age".getBytes())));

		// Result rs = dao.getOneRowAndMultiColumn("cell_monitor_table",
		// "29448-513332015-04-05", new
		// String[]{"201504052236","201504052237"});
		// for(Cell cell:rs.rawCells()){
		// System.out.println("RowName:"+new String(CellUtil.cloneRow(cell))+"
		// ");
		// System.out.println("Timetamp:"+cell.getTimestamp()+" ");
		// System.out.println("column Family:"+new
		// String(CellUtil.cloneFamily(cell))+" ");
		// System.out.println("row Name:"+new
		// String(CellUtil.cloneQualifier(cell))+" ");
		// System.out.println("value:"+new String(CellUtil.cloneValue(cell))+"
		// ");
		// }

		// dao.deleteTable("cell_monitor_table");
		// 創建表
		String tableName = "cell_monitor_table";
		String cfs[] = { "cf" };
		// dao.createTable(tableName,cfs);
	}

	public static void testRowFilter(String tableName) {
		try {
			HTable table = new HTable(conf, tableName);
			Scan scan = new Scan();
			scan.addColumn(Bytes.toBytes("column1"), Bytes.toBytes("qqqq"));
			Filter filter1 = new RowFilter(CompareOp.LESS_OR_EQUAL, new BinaryComparator(Bytes.toBytes("laoxia157")));
			scan.setFilter(filter1);
			ResultScanner scanner1 = table.getScanner(scan);
			for (Result res : scanner1) {
				System.out.println(res);
			}
			scanner1.close();

			//
			// Filter filter2 = new RowFilter(CompareFilter.CompareOp.EQUAL,new
			// RegexStringComparator("laoxia4\\d{2}"));
			// scan.setFilter(filter2);
			// ResultScanner scanner2 = table.getScanner(scan);
			// for (Result res : scanner2) {
			// System.out.println(res);
			// }
			// scanner2.close();

			Filter filter3 = new RowFilter(CompareOp.EQUAL, new SubstringComparator("laoxia407"));
			scan.setFilter(filter3);
			ResultScanner scanner3 = table.getScanner(scan);
			for (Result res : scanner3) {
				System.out.println(res);
			}
			scanner3.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Test
	public void testTrasaction() {
		try {
			HTableInterface table = null;
			table = hTablePool.getTable("t_test".getBytes());
			// Put put1 =new Put("002".getBytes());
			// put1.add("cf1".getBytes(), "name".getBytes(), "王五".getBytes());
			// table.put(put1);
			Put newput = new Put("001".getBytes());
			newput.add("cf1".getBytes(), "like".getBytes(), "看書".getBytes());

			boolean f = table.checkAndPut("001".getBytes(), "cf1".getBytes(), "age".getBytes(), "24".getBytes(),
					newput);
			System.out.println(f);

		} catch (Exception e) {
			e.printStackTrace();
		}

	}
}

HBase 微博案例

目的:怎麼設計 HBase 表以及內部 RowKey 的設計

需求

1. 添加、查看關注
2. 粉絲列表
3. 寫微博
4. 查看首頁, 所有關注過的好友發佈的最新微博
5. 查看某個用戶發佈的所有微博

eg:               關注列表 					粉絲列表
張三001             李四				       王五
李四002                                      張三,王五
王五003  	           張三					  張三

follow_fan
rowkey  		 cf1(關注列表)                cf2(粉絲列表)
001				 002=李四;					   003=王五;
002              	                            001=張三;003=王五                      
003              001=張三;					   001=張三

tb_write_blog
rowkey      					cf1
uid_(Max-timestamp)             cf1:title;cf1:content;

writed_blog_history_version

Protobuf

Protocol Buffers 是一種輕便高效的結構化數據存儲格式,可以用於結構化數據串行化,或者說序列化

Protobuf 簡介

Google Protocol Buffer( 簡稱 Protobuf) 是 Google 公司內部的混合語言數據標準。

目前已經正在使用的有超過 48,162 種報文格式定義和超過 12,183 個 .proto 文件。

他們用於 RPC 系統和持續數據存儲系統。

Protocol Buffers 是一種輕便高效的結構化數據存儲格式,可以用於結構化數據串行化,或者說序列化

它很適合做數據存儲或 RPC 數據交換格式。

可用於通訊協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。

目前提供了 C++、Java、Python 三種語言的 API。

安裝 Google Protocol Buffer

yum groupinstall Development tools -y

tar -xzf protobuf-2.1.0.tar.gz 
# 配置
.cofigure --prefix=/usr/local/protobuf

#編譯並安裝
make && make install 

#配置環境變量
export PROTOBUF=/usr/local/protobuf

protobuf 的使用

# 編輯 vi  Phone.proto 文件
package com.szxy.hbase;
message PhoneDetail{
	required string dnum=1;   // 被叫電話
	required string type=2;  // 通話類型
	required string length=3;  // 通話時長
	required string datastr=4; // 通話時間
} 

# 執行 
protoc ./Phone --java_out=./

# 將生成的 java 文件拷貝到本地 Eclipse 上

@Test
	public void addPhoneRecord2() throws Exception{
		List<Put> puts = new ArrayList<>();
		for(int i = 0;i < 10;i++){
			String phoneNum = generatePhoneNumber("189");
			for(int j = 0;j < 100;j++){
				String dnum = generatePhoneNumber("138");
				String length = r.nextInt(99)+""; // 通話時長
				String type = r.nextInt(2)+""; // 1 是主叫,2 是被叫
				String dataStr  = getDate("2019");  // 通話開始時間
				String rowkey =
                phoneNum+"_"+(Long.MAX_VALUE-sdf.parse(dataStr).getTime());
				Builder builder
                = Phone.PhoneDetail.getDefaultInstance().newBuilderForType();
				builder.setDnum(dnum);
				builder.setLength(length);
				builder.setType(type);
				builder.setDatastr(dataStr);
				Put put 
				= new Put(rowkey.getBytes());
			put.add("cf".getBytes(),"phoneDetail".getBytes(),builder.build().toByteArray());
				puts.add(put);
			}
		}
		htable.put(puts);
	}
	
# 編輯 vi  phoneDetail.proto 文件
package com.szxy.hbase;

message PhoneDetail
{
    required string dnum=1;  
    required string type=2; 
    required string length=3; 
    required string datastr=4;
} 
message dayPhoneDetail
{
	repeated PhoneDetail  phoneDetail = 1;
}

/**
	 * 生成 10 個人一天的通話記錄
	 * 每行中 列中存儲 100 條通話記錄
	 *
	 */
	@Test
	public void addPhoneRecord3() throws Exception{
		List<Put> puts = new ArrayList<>();
		for(int i = 0;i < 10;i++){
			String phoneNum = generatePhoneNumber("189");
			String rowkey = phoneNum+"_"+(Long.MAX_VALUE-sdf.parse(getDate2("20190801")).getTime());
			Phone.dayPhoneDetail.Builder dayPhoneDetail = Phone.dayPhoneDetail.newBuilder();
			for(int j = 0;j < 100;j++){
				String dnum = generatePhoneNumber("138");
				String length = r.nextInt(99)+""; // 通話時長
				String type = r.nextInt(2)+""; // 1 是主叫,2 是被叫
				String dataStr  = getDate("2019");  // 通話開始時間
				PhoneDetail.Builder phoneDetail = Phone.PhoneDetail.newBuilder();
				phoneDetail.setDnum(dnum);
				phoneDetail.setLength(length);
				phoneDetail.setType(type);
				phoneDetail.setDatastr(dataStr);
				dayPhoneDetail.addPhoneDetail(phoneDetail);
			}
			Put put = new Put(rowkey.getBytes());
			put.add("cf".getBytes(),"day".getBytes(),dayPhoneDetail.build().toByteArray());
			puts.add(put);
		}
		htable.put(puts);
	}
	
	/**
	 * 查看數據
	 * @throws IOException
	 */
	@Test
	public void getData() throws IOException{
		Get get = new Get("18962786962_9223370472247815807".getBytes());
		Result result = htable.get(get);
		Cell cell = result.getColumnLatestCell("cf".getBytes(), "day".getBytes());
		dayPhoneDetail dayPhoneDetail = Phone.dayPhoneDetail.parseFrom(CellUtil.cloneValue(cell));
		List<PhoneDetail> phoneDetailList = dayPhoneDetail.getPhoneDetailList();
		int count = 0;
		for (PhoneDetail pd : phoneDetailList) {
			System.out.println(pd.getDnum()+"\t"+pd.getType()+"\t"+pd.getLength()+"\t"+pd.getDatastr());
			count ++;
		}
		System.out.println("count:"+count);
	}

HBase 優化

表設計

  • Region 切分

    在表設計時,預先估計表中的數據,並切分多個 region,防止數據過多,導致單個服務器過載。

    Region 是按照 Rowkey 來切分的

  • Rowkey 設計

    Rowkey 按照字典序排列(即 ASCII )。Rowkey 的長度不要太長,符合業務需求即可。

  • ColumnFamily 設計

    列族控制在 1~2 之間,不要超過 3 個列族

  • InMemory

    HBase 中緩存分爲寫緩存和讀緩存。通過 HColumnDescriptor.setInMemory(true) 將表放到RegionServer的緩存中,保證在讀取的時候被cache命中。

  • compact & split

    在 HBase 中,數據在更新時首先寫入 WAL 日誌(HLog)和內存(MemStore)中,MemStore 中的數據是排序的,當 MemStore 累計到一定閾值時,就會創建一個新的 MemStore,並且將老的 MemStore 添加到 flush隊列,由單獨的線程 flush 到磁盤上,成爲一個 StoreFile 。於此同時, 系統會在 zookeeper 中記錄一個 redo point,表示這個時刻之前的變更已經持久化了(minor compact)。
    StoreFile 是隻讀的,一旦創建後就不可以再修改。因此 Hbase 的更新其實是不斷追加的操作。當一個 Store中的 StoreFile 達到一定的閾值後,就會進行一次合併(major compact),將對同一個 key 的修改合併到一起,形成一個大的 StoreFile,當 StoreFile 的大小達到一定閾值後,又會對 StoreFile進行分割(split),等分爲兩個StoreFile。
    由於對錶的更新是不斷追加的,處理讀請求時,需要訪問Store中全部的StoreFile和MemStore,將它們按照row key進行合併,由於StoreFile和MemStore都是經過排序的,並且StoreFile帶有內存中索引,通常合併過程還是比較快的。
    實際應用中,可以考慮必要時手動進行major compact,將同一個row key的修改進行合併形成一個大的StoreFile。同時,可以將StoreFile設置大些,減少split的發生。

    hbase爲了防止小文件(被刷到磁盤的menstore)過多,以保證保證查詢效率,hbase需要在必要的時候將這些小的store file合併成相對較大的store file,這個過程就稱之爲compaction。在hbase中,主要存在兩種類型的compaction:minor compaction和major compaction。
    minor compaction:的是較小、很少文件的合併。
    major compaction 的功能是將所有的store file合併成一個,觸發major compaction的可能條件有:major_compact 命令、majorCompact() API、region server自動運行(相關參數:hbase.hregion.majoucompaction 默認爲24 小時、hbase.hregion.majorcompaction.jetter 默認值爲0.2 防止region server 在同一時間進行major compaction)。
    hbase.hregion.majorcompaction.jetter參數的作用是:對參數hbase.hregion.majoucompaction 規定的值起到浮動的作用,假如兩個參數都爲默認值24和0,2,那麼major compact最終使用的數值爲:19.2~28.8 這個範圍。

    1、關閉自動major compaction
    2、手動編程major compaction
    Timer類,contab
    minor compaction的運行機制要複雜一些,它由一下幾個參數共同決定:
    hbase.hstore.compaction.min :默認值爲 3,表示至少需要三個滿足條件的store file時,minor compaction纔會啓動
    hbase.hstore.compaction.max 默認值爲10,表示一次minor compaction中最多選取10個store file
    hbase.hstore.compaction.min.size 表示文件大小小於該值的store file 一定會加入到minor compaction的store file中
    hbase.hstore.compaction.max.size 表示文件大小大於該值的store file 一定會被minor compaction排除
    hbase.hstore.compaction.ratio 將store file 按照文件年齡排序(older to younger),minor compaction總是從older store file開始選擇

寫表操作

  1. 多 Table 併發寫

創建多個HTable客戶端用於寫操作,提高寫數據的吞吐量,一個例子:

static final Configuration conf = HBaseConfiguration.create();
static final String table_log_name = “user_log”;
wTableLog = new HTable[tableN];
for (int i = 0; i < tableN; i++) {
    wTableLog[i] = new HTable(conf, table_log_name);
    wTableLog[i].setWriteBufferSize(5 * 1024 * 1024); //5MB
    wTableLog[i].setAutoFlush(false);
}
  1. HTable 參數設置:

    • Auto Flush

      通過調用HTable.setAutoFlush(false)方法可以將HTable寫客戶端的自動flush關閉,這樣可以批量寫入數據到HBase,而不是有一條put就執行一次更新,只有當put填滿客戶端寫緩存時,才實際向HBase服務端發起寫請求。默認情況下auto flush是開啓的。

    • Write Buffer

      通過調用HTable.setWriteBufferSize(writeBufferSize)方法可以設置HTable客戶端的寫buffer大小,如果新設置的buffer小於當前寫buffer中的數據時,buffer將會被flush到服務端。其中,writeBufferSize的單位是byte字節數,可以根據實際寫入數據量的多少來設置該值。

    • WAL Flag

      在HBae中,客戶端向集羣中的RegionServer提交數據時(Put/Delete操作),首先會先寫WAL(Write Ahead Log)日誌(即HLog,一個RegionServer上的所有Region共享一個HLog),只有當WAL日誌寫成功後,再接着寫MemStore,然後客戶端被通知提交數據成功;如果寫WAL日誌失敗,客戶端則被通知提交失敗。這樣做的好處是可以做到RegionServer宕機後的數據恢復。

      因此,對於相對不太重要的數據,可以在Put/Delete操作時,通過調用Put.setWriteToWAL(false)或Delete.setWriteToWAL(false)函數,放棄寫WAL日誌,從而提高數據寫入的性能。

      值得注意的是:謹慎選擇關閉WAL日誌,因爲這樣的話,一旦RegionServer宕機,Put/Delete的數據將會無法根據WAL日誌進行恢復。

  2. 批量寫

    通過調用HTable.put(Put)方法可以將一個指定的row key記錄寫入HBase,同樣HBase提供了另一個方法:通過調用HTable.put(List)方法可以將指定的row key列表,批量寫入多行記錄,這樣做的好處是批量執行,只需要一次網絡I/O開銷,這對於對數據實時性要求高,網絡傳輸RTT高的情景下可能帶來明顯的性能提升。

  3. 多線程併發寫

    在客戶端開啓多個HTable寫線程,每個寫線程負責一個HTable對象的flush操作,這樣結合定時flush和寫buffer(writeBufferSize),可以既保證在數據量小的時候,數據可以在較短時間內被flush(如1秒內),同時又保證在數據量大的時候,寫buffer一滿就及時進行flush。下面給個具體的例子:

    for (int i = 0; i < threadN; i++) {
        Thread th = new Thread() {
            public void run() {
                while (true) {
                    try {
                        sleep(1000); //1 second
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    synchronized (wTableLog[i]) {
                        try {
                            wTableLog[i].flushCommits();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
    }
        };
        th.setDaemon(true);
        th.start();
    }
    

讀表操作

  1. 多 HTable 併發讀

創建多個HTable客戶端用於讀操作,提高讀數據的吞吐量,一個例子:

static final Configuration conf = HBaseConfiguration.create();
static final String table_log_name = “user_log”;
rTableLog = new HTable[tableN];
for (int i = 0; i < tableN; i++) {
    rTableLog[i] = new HTable(conf, table_log_name);
    rTableLog[i].setScannerCaching(50);
}
  1. HTable 參數設置
  • Scanner Caching
    hbase.client.scanner.caching配置項可以設置HBase scanner一次從服務端抓取的數據條數,默認情況下一次一條。通過將其設置成一個合理的值,可以減少scan過程中next()的時間開銷,代價是scanner需要通過客戶端的內存來維持這些被cache的行記錄。
    有三個地方可以進行配置:1)在HBase的conf配置文件中進行配置;2)通過調用HTable.setScannerCaching(int scannerCaching)進行配置;3)通過調用Scan.setCaching(int caching)進行配置。三者的優先級越來越高。
  • Scan Attribute Selection
    scan時指定需要的Column Family,可以減少網絡傳輸數據量,否則默認scan操作會返回整行所有Column Family的數據。
  • Close ResultScanner
    通過scan取完數據後,記得要關閉ResultScanner,否則RegionServer可能會出現問題(對應的Server資源無法釋放)。
  1. 批量讀

    通過調用HTable.get(Get)方法可以根據一個指定的row key獲取一行記錄,同樣HBase提供了另一個方法:通過調用HTable.get(List)方法可以根據一個指定的row key列表,批量獲取多行記錄,這樣做的好處是批量執行,只需要一次網絡I/O開銷,這對於對數據實時性要求高而且網絡傳輸RTT高的情景下可能帶來明顯的性能提升。

  2. 多線程併發讀

    在客戶端開啓多個HTable讀線程,每個讀線程負責通過HTable對象進行get操作。下面是一個多線程併發讀取HBase,獲取店鋪一天內各分鐘PV值的例子:

    public class DataReaderServer {
         //獲取店鋪一天內各分鐘PV值的入口函數
         public static ConcurrentHashMap<String, String> getUnitMinutePV(long uid, long startStamp, long endStamp){
             long min = startStamp;
             int count = (int)((endStamp - startStamp) / (60*1000));
             List<String> lst = new ArrayList<String>();
             for (int i = 0; i <= count; i++) {
                min = startStamp + i * 60 * 1000;
                lst.add(uid + "_" + min);
             }
             return parallelBatchMinutePV(lst);
         }
          //多線程併發查詢,獲取分鐘PV值
    private static ConcurrentHashMap<String, String> parallelBatchMinutePV(List<String> lstKeys){
            ConcurrentHashMap<String, String> hashRet = new ConcurrentHashMap<String, String>();
            int parallel = 3;
            List<List<String>> lstBatchKeys  = null;
            if (lstKeys.size() < parallel ){
                lstBatchKeys  = new ArrayList<List<String>>(1);
                lstBatchKeys.add(lstKeys);
            }
            else{
                lstBatchKeys  = new ArrayList<List<String>>(parallel);
                for(int i = 0; i < parallel; i++  ){
                    List<String> lst = new ArrayList<String>();
                    lstBatchKeys.add(lst);
                }
    
                for(int i = 0 ; i < lstKeys.size() ; i ++ ){
                    lstBatchKeys.get(i%parallel).add(lstKeys.get(i));
                }
            }
            
            List<Future< ConcurrentHashMap<String, String> >> futures = new ArrayList<Future< ConcurrentHashMap<String, String> >>(5);
            
            ThreadFactoryBuilder builder = new ThreadFactoryBuilder();
            builder.setNameFormat("ParallelBatchQuery");
            ThreadFactory factory = builder.build();
            ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(lstBatchKeys.size(), factory);
            
            for(List<String> keys : lstBatchKeys){
                Callable< ConcurrentHashMap<String, String> > callable = new BatchMinutePVCallable(keys);
                FutureTask< ConcurrentHashMap<String, String> > future = (FutureTask< ConcurrentHashMap<String, String> >) executor.submit(callable);
                futures.add(future);
            }
            executor.shutdown();
            
            // Wait for all the tasks to finish
            try {
              boolean stillRunning = !executor.awaitTermination(
                  5000000, TimeUnit.MILLISECONDS);
              if (stillRunning) {
                try {
                    executor.shutdownNow();
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
              }
            } catch (InterruptedException e) {
              try {
                  Thread.currentThread().interrupt();
              } catch (Exception e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
              }
            }
            
            // Look for any exception
            for (Future f : futures) {
              try {
                  if(f.get() != null)
                  {
                      hashRet.putAll((ConcurrentHashMap<String, String>)f.get());
                  }
              } catch (InterruptedException e) {
                try {
                     Thread.currentThread().interrupt();
                } catch (Exception e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
              } catch (ExecutionException e) {
                e.printStackTrace();
              }
            }
            
            return hashRet;
        }
         //一個線程批量查詢,獲取分鐘PV值
        protected static ConcurrentHashMap<String, String> getBatchMinutePV(List<String> lstKeys){
            ConcurrentHashMap<String, String> hashRet = null;
            List<Get> lstGet = new ArrayList<Get>();
            String[] splitValue = null;
            for (String s : lstKeys) {
                splitValue = s.split("_");
                long uid = Long.parseLong(splitValue[0]);
                long min = Long.parseLong(splitValue[1]);
                byte[] key = new byte[16];
                Bytes.putLong(key, 0, uid);
                Bytes.putLong(key, 8, min);
                Get g = new Get(key);
                g.addFamily(fp);
                lstGet.add(g);
            }
            Result[] res = null;
            try {
                res = tableMinutePV[rand.nextInt(tableN)].get(lstGet);
            } catch (IOException e1) {
                logger.error("tableMinutePV exception, e=" + e1.getStackTrace());
            }
    
            if (res != null && res.length > 0) {
                hashRet = new ConcurrentHashMap<String, String>(res.length);
                for (Result re : res) {
                    if (re != null && !re.isEmpty()) {
                        try {
                            byte[] key = re.getRow();
                            byte[] value = re.getValue(fp, cp);
                            if (key != null && value != null) {
                                hashRet.put(String.valueOf(Bytes.toLong(key,
                                        Bytes.SIZEOF_LONG)), String.valueOf(Bytes
                                        .toLong(value)));
                            }
                        } catch (Exception e2) {
                            logger.error(e2.getStackTrace());
                        }
                    }
                }
            }
    
            return hashRet;
        }
    }
    //調用接口類,實現Callable接口
    class BatchMinutePVCallable implements Callable<ConcurrentHashMap<String, String>>{
         private List<String> keys;
    
         public BatchMinutePVCallable(List<String> lstKeys ) {
             this.keys = lstKeys;
         }
    
         public ConcurrentHashMap<String, String> call() throws Exception {
             return DataReadServer.getBatchMinutePV(keys);
         }
    }
    
  3. 緩存查詢

    對於頻繁查詢HBase的應用場景,可以考慮在應用程序中做緩存,當有新的查詢請求時,首先在緩存中查找,如果存在則直接返回,不再查詢HBase;否則對HBase發起讀請求查詢,然後在應用程序中將查詢結果緩存起來。至於緩存的替換策略,可以考慮LRU等常用的策略。

  4. BlockCache

    HBase上Regionserver 的內存分爲兩個部分,一部分作爲 Memstore,主要用來寫;另外一部分作爲BlockCache,主要用於讀。

    寫請求會先寫入Memstore,Regionserver會給每個region提供一個Memstore,當Memstore滿64MB以後,會啓動 flush刷新到磁盤。當Memstore的總大小超過限制時(heapsize * hbase.regionserver.global.memstore.upperLimit * 0.9),會強行啓動flush進程,從最大的Memstore開始flush直到低於限制。

    讀請求先到Memstore中查數據,查不到就到BlockCache中查,再查不到就會到磁盤上讀,並把讀的結果放入BlockCache。由於BlockCache採用的是LRU策略,因此BlockCache達到上限(heapsize * hfile.block.cache.size * 0.85)後,會啓動淘汰機制,淘汰掉最老的一批數據。

    一個Regionserver上有一個BlockCache和N個Memstore,它們的大小之和不能大於等於heapsize * 0.8,否則HBase不能啓動。默認BlockCache爲0.2,而Memstore爲0.4。對於注重讀響應時間的系統,可以將 BlockCache設大些,比如設置BlockCache=0.4,Memstore=0.39,以加大緩存的命中率。

HTable 和 HTablePool使用注意事項

HTable和HTablePool都是HBase客戶端API的一部分,可以使用它們對HBase表進行CRUD操作。下面結合在項目中的應用情況,對二者使用過程中的注意事項做一下概括總結。

Configuration conf = HBaseConfiguration.create();

try (Connection connection = ConnectionFactory.createConnection(conf)) {

  try (Table table = connection.getTable(TableName.valueOf(tablename)) {

      // use table as needed, the table returned is lightweight

  }

}

HTable

HTable是HBase客戶端與HBase服務端通訊的Java API對象,客戶端可以通過HTable對象與服務端進行CRUD操作(增刪改查)。它的創建很簡單:

Configuration conf = HBaseConfiguration.create();

HTable table = new HTable(conf, "tablename");

//TODO CRUD Operation……

HTable使用時的一些注意事項:

  1. 規避 HTable 對象的創建開銷
    因爲客戶端創建 HTable 對象後,需要進行一系列的操作:檢查.META.表確認指定名稱的HBase表是否存在,表是否有效等等,整個時間開銷比較重,可能會耗時幾秒鐘之長,因此最好在程序啓動時一次性創建完成需要的HTable對象,如果使用 Java API,一般來說是在構造函數中進行創建,程序啓動後直接重用。

  2. HTable 對象不是線程安全的
    HTable 對象對於客戶端讀寫數據來說不是線程安全的,因此多線程時,要爲每個線程單獨創建複用一個HTable 對象,不同對象間不要共享HTable對象使用,特別是在客戶端auto flash被置爲false時,由於存在本地write buffer,可能導致數據不一致。

  3. HTable對象之間共享Configuration
    HTable對象共享Configuration對象,這樣的好處在於:

  • 共享ZooKeeper的連接:每個客戶端需要與ZooKeeper建立連接,查詢用戶的table regions位置,這些信息可以在連接建立後緩存起來共享使用;
  • 共享公共的資源:客戶端需要通過ZooKeeper查找-ROOT-和.META.表,這個需要網絡傳輸開銷,客戶端緩存這些公共資源後能夠減少後續的網絡傳輸開銷,加快查找過程速度。

因此,與以下這種方式相比:

HTable table1 = new HTable("table1");

HTable table2 = new HTable("table2");

下面的方式更有效些:

Configuration conf = HBaseConfiguration.create();

HTable table1 = new HTable(conf, "table1");

HTable table2 = new HTable(conf, "table2");

備註:即使是高負載的多線程程序,也並沒有發現因爲共享Configuration而導致的性能問題;如果你的實際情況中不是如此,那麼可以嘗試不共享Configuration。

HTablePool

HTablePool可以解決HTable存在的線程不安全問題,同時通過維護固定數量的HTable對象,能夠在程序運行期間複用這些HTable資源對象。

Configuration conf = HBaseConfiguration.create();

HTablePool pool = new HTablePool(conf, 10);
  1. HTablePool可以自動創建HTable對象,而且對客戶端來說使用上是完全透明的,可以避免多線程間數據併發修改問題。

  2. HTablePool中的HTable對象之間是公用Configuration連接的,能夠可以減少網絡開銷。

HTablePool的使用很簡單:每次進行操作前,通過HTablePool的getTable方法取得一個HTable對象,然後進行put/get/scan/delete等操作,最後通過 HTablePool 的 putTable 方法將HTable對象放回到HTablePool中。

下面是個使用HTablePool的簡單例子:

public void createUser(String username, String firstName, String lastName, String email, String password, String roles) throws IOException {

  HTable table = rm.getTable(UserTable.NAME);

  Put put = new Put(Bytes.toBytes(username));

  put.add(UserTable.DATA_FAMILY, UserTable.FIRSTNAME,

  Bytes.toBytes(firstName));

  put.add(UserTable.DATA_FAMILY, UserTable.LASTNAME,

    Bytes.toBytes(lastName));

  put.add(UserTable.DATA_FAMILY, UserTable.EMAIL, Bytes.toBytes(email));

  put.add(UserTable.DATA_FAMILY, UserTable.CREDENTIALS,

    Bytes.toBytes(password));

  put.add(UserTable.DATA_FAMILY, UserTable.ROLES, Bytes.toBytes(roles));

  table.put(put);

  table.flushCommits();

  rm.putTable(table);

}

Hbase和DBMS比較:

查詢數據不靈活:

  1. 不能使用column之間過濾查詢

  2. 不支持全文索引。使用solr和hbase整合完成全文搜索。

    a) 使用MR批量讀取hbase中的數據,在solr裏面建立索引(no store)之保存rowkey的值。

    b) 根據關鍵詞從索引中搜索到rowkey(分頁)

    c) 根據rowkey從hbase查詢所有數據

HBase-MapReduce 整合


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章