大數據面試之HBase
說明,感謝亮哥長期對我的幫助,此處多篇文章均爲亮哥帶我整理。以及參考諸多博主的文章。如果侵權,請及時指出,我會立馬停止該行爲;如有不足之處,還請大佬不吝指教,以期共同進步。
1.HBase
1.1 HBase的架構模型?
1、HMaster
負責管理HBase元數據,即表的結構、表存儲的Region等元信息。
負責表的創建,刪除和修改(因爲這些操作會導致HBase元數據的變動)。
負責爲HRegionServer分配Region,分配好後也會將元數據寫入相應位置。
2、HRegionServer
一個RegionServer裏有多個Region。
處理Client端的讀寫請求(根據從HMaster返回的元數據找到對應的Region來讀寫數據)。
管理Region的Split分裂、StoreFile的Compaction合併。
一個RegionServer管理着多個Region,在HBase運行期間,可以動態添加、刪除HRegionServer。
3、HRegion
一個HRegion裏可能有1個或多個Store。
HRegionServer維護一個HLog。
HRegion是分佈式存儲和負載的最小單元。
表通常被保存在多個HRegionServer的多個Region中。
4、Store
Store是存儲落盤的最小單元,由內存中的MemStore和磁盤中的若干StoreFile組成。
一個Store裏有1個或多個StoreFile和一個memStore。
每個Store存儲一個列族。
Region是HBase數據存儲和管理的基本單位。在HBase的一個表中,可以包含一個或多個region。
對於一個region,每個列族都會對應一個store,用來存儲該列族的數據。
每個store都有一個寫緩存memstore,用於緩存寫入的數據。
1.2 HBase讀寫流程
1.2.1 寫過程
(1) Client先從緩存中定位region,如果沒有緩存則需訪問zookeeper,從.META.表獲取要寫入的region信息
(2) 找到小於rowkey並且最接近rowkey的startkey對應的region
(3) 將更新寫入WAL中。當客戶端發起put/delete請求時,考慮到寫入內存會有丟失數據的風險,
因此在寫入緩存前,HBase會先寫入到Write Ahead Log(WAL)中(WAL存儲在HDFS中),
那麼即使發生宕機,也可以通過WAL還原初始數據。
(4) 將更新寫入memstore中,當增加到一定大小,達到預設的Flush size閾值時,會觸發flush memstore,
把memstore中的數據寫出到hdfs上,生成一個storefile。
(5) 隨着Storefile文件的不斷增多,當增長到一定閾值後,觸發compact合併操作,
將多個storefile合併成一個,同時進行版本合併和數據刪除。
(6) storefile通過不斷compact合併操作,逐步形成越來越大的storefile。
(7) 單個stroefile大小超過一定閾值後,觸發split操作,把當前region拆分成兩個,
新拆分的2個region會被hbase master分配到相應的2個regionserver上。
1.2.2 讀過程
(1) Client先從緩存中定位region,如果沒有緩存則需訪問zookeeper,查詢.-ROOT-.表,
獲取.-ROOT-.表所在的regionserver地址。
(2) 通過查詢.-ROOT-.的region服務器 獲取 含有.-META-.表所在的regionserver地址。
(3) clinet會將保存着regionserver位置信息的元數據表.META.進行緩存,
然後在表中確定待檢索rowkey所在的regionserver信息。
(4) client會向在.META.表中確定的regionserver發送真正的數據讀取請求。
(5) 先從memstore中找,如果沒有,再到storefile上讀。
1.3 HBase查詢速度爲什麼快
採用了LSM樹型結構(日誌結構合併樹),而不是B或B+樹。
B+樹最大的性能問題是會產生大量的隨機I/O,低下的磁盤尋道速度。
爲了讀性能的提高,數據在磁盤中必須有序,這就是B+樹的原理。但是寫就悲劇了,key跨度很大的情況,
新插入的數據存儲在磁盤上相隔很遠的地方,會產生大量的隨機IO,磁盤尋道速度跟不上。
對於LSM樹來說,犧牲了部分讀性能,來大幅提高寫性能。
原理是:將一顆大樹拆分成N顆小樹,首先寫入內存(內存中的數據是有序的,內存沒有尋道速度的問題,隨機寫的性能得到大幅提升),
在內存中構建一顆有序小樹(memstore),隨着其越來越大,flush到磁盤上,成爲一個storefile,該storefile中的數據是有序的。
1.4 Hbase數據熱點的措施
hbase的表的多個region中有一個region的讀寫併發很高,其他的region相對來說讀寫少,造成熱點的region
防止數據熱點的有效措施
1. 加鹽
這裏所說的加鹽不是密碼學中的加鹽,而是在 rowkey 的前面增加隨機數,具體就是給rowkey 分配一個隨機前綴以使得它和之前的rowkey 的開頭不同。分配的前綴種類數量應該和你想使用數據分散到不同的 region 的數量一致。加鹽之後的 rowkey就會根據隨機生成的前綴分散到各個region上,以避免熱點。
2.哈希
哈希會使同一行永遠用一個前綴加鹽。哈希也可以使負載分散到整個集羣,但是讀卻是可以預測的。使用確定的哈希可以讓客戶端重構完整的 rowkey,可以使用 get 操作準確獲取某一個行數據
3. 反轉
第三種防止熱點的方法是反轉固定長度或者數字格式的 rowkey。這樣可以使得 rowkey中經常改變的部分(最沒有意義的部分)放在前面。這樣可以有效的隨機 rowkey,但是犧 牲了 rowkey 的有序性。
反轉 rowkey 的例子以手機號爲 rowkey,可以將手機號反轉後的字符串作爲 rowkey,這 樣的就避免了以手機號那樣比較固定開頭導致熱點問題
4. 時間戳反轉
一個常見的數據處理問題是快速獲取數據的最近版本,使用反轉的時間戳作爲 rowkey的一部分對這個問題十分有用,可以用 Long.Max_Value - timestamp 追加到 key 的末尾,例 如 [key][reverse_timestamp] , [key] 的最新值可以通過 scan [key]獲得[key]的第一條記錄,因 爲 HBase 中 rowkey 是有序的,第一條記錄是最後錄入的數據。比如需要保存一個用戶的操作記錄,按照操作時間倒序排序,在設計 rowkey 的時候,可以這樣設計[userId 反轉][Long.Max_Value - timestamp],在查詢用戶的所有操作記錄數據的時候,直接指 定反轉後的 userId,startRow 是[userId 反轉][000000000000],stopRow 是[userId 反 轉][Long.Max_Value - timestamp]如果需要查詢某段時間的操作記錄,startRow 是[user 反轉][Long.Max_Value - 起始時間], stopRow 是[userId 反轉][Long.Max_Value - 結束時間]
1.5 HBase的特點是什麼?
(1)hbase是一個分佈式的基於列式存儲的數據庫,基於hadoop的HDFS存儲,zookeeper進行管理。
(2)hbase適合存儲半結構化或非結構化數據,對於數據結構字段不夠確定或者雜亂無章很難按一個概念去抽取的數據。
(3)hbase爲null的記錄不會被存儲。
(4)基於的表包括rowkey,時間戳和列族。新寫入數據時,時間戳更新,同時可以查詢到以前的版本。
(5)hbase是主從結構。Hmaster作爲主節點,hregionserver作爲從節點。
1.6 HBase中region太小和region太大帶來的結果
Region過大會發生多次compaction,將數據讀一遍並寫一遍到hdfs上,佔用io
Region過小會造成多次split,region會下線,影響訪問服務,調整hbase.heregion.max.filesize爲256m。
1.6 JAVA訪問HBase、JPA訪問HBase
JAVA訪問HBase
//第一步,設置HBsae配置信息
Configuration configuration = HBaseConfiguration.create();
//注意。這裏這行目前沒有註釋掉的,這行和問題3有關係 是要根據自己zookeeper.znode.parent的配置信息進行修改。
configuration.set("zookeeper.znode.parent","/hbase-unsecure"); //與 hbase-site-xml裏面的配置信息 zookeeper.znode.parent 一致
configuration.set("hbase.zookeeper.quorum","192.168.8.30"); //hbase 服務地址
configuration.set("hbase.zookeeper.property.clientPort","2181"); //端口號
//這裏使用的是接口Admin 該接口有一個實現類HBaseAdmin 也可以直接使用這個實現類
// HBaseAdmin baseAdmin = new HBaseAdmin(configuration);
Admin admin = ConnectionFactory.createConnection(configuration).getAdmin();
if(admin !=null){
try {
//獲取到數據庫所有表信息
HTableDescriptor[] allTable = admin.listTables();
for (HTableDescriptor hTableDescriptor : allTable) {
System.out.println(hTableDescriptor.getNameAsString());
}
}catch (IOException e) {
e.printStackTrace();
}
參考
參考
參考
參考
參考
使用kundera jpa操作hbase
kundera是一個兼容jpa接口的對象映射器。當前kundera支持的數據庫有:
Cassandra,MongoDB,HBase,Redis,OracleNoSQL,Neo4j,CouchDB,Dudu,Relational databases,Apache Spark
persistence.xml內容如下
<!--指定供應商地址,固定的使用kundera -->
<provider>com.impetus.kundera.KunderaPersistence</provider>
<!-- 指定映射的類的全路徑 -->
<class>com.zxz.entity.Person</class>
<properties>
<!-- 配置zookeeper節點服務器ip -->
<property name="kundera.nodes" value="192.168.14.125"/>
<!-- 配置zookeeper rpc連接端口 -->
<property name="kundera.port" value="2181"/>
<!-- 配置hbase名字空間和表內容 -->
<property name="kundera.keyspace" value="zxz:testZ"/>
<!-- 配置hbase方言 -->
<property name="kundera.dialect" value="hbase"/>
<!-- 配置hbase依賴類 -->
<property name="kundera.client.lookup.class" value="com.impetus.client.hbase.HBaseClientFactory"/>
</properties>
編寫實體類也就是上面的配置映射的類,hbase的表一一對應
@Entity
//指定hbase表中的列族
@Table(name="zxz")
public class Person {
//指定rowkey
@Id
private int id;
//指定列
@Column
private String name;
@Column
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Person [age=" + age + ", id=" + id + ", name=" + name + "]";
}
}
首先在代碼前介紹1個類和兩個接口,他們就是來銜接hbase表和實體類進行底層實現的
1,Persistence(持久)類
引導類,用於在Java SE環境中獲取EntityManagerFactory接口。
2,EntityManagerFactory(實體管理工廠)接口
用於與持久單元的實體管理器工廠交互的接口。
3,ManagerFactory(實體管理)接口
用於與持久上下文交互的接口EntityManager實例與持久化上下文關聯。持久性上下文是一組實體類實例,其
中對於任何持久實體標識,都有一個惟一的實體類實例。在持久化上下文中,管理實體實例及其生命週期。
EntityManager API用於創建和刪除持久實體實例,通過其主鍵查找實體,並查詢實體。前面兩個都是爲這
接口鋪墊的。
以上都是從源碼摘下來的
public class hbaseJpa {
//CRUD(增C刪D改U查R)
//1,C 增加
@Test
public void insert() {
// 創建出實體管理對象進行對對象的操作來實現前兩行是固定寫法
// persistence類調用抽象方法createEntityManagerFactory("參數爲當前項目名")方法來獲取EntityManagerFactory接口
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mykundera");
// 大家可能會有疑惑說接口能調用方法呢,有空的同學可以看一下他其實返回的是EntityManagerFactoryImpl這個實現類,
// 只不過利用了多態的方式返回了實現類的接口,其實和List<> list=new ArrayList<>();差不多
// EntityManger接口其實和EntityManagerFactory一樣的實現方式都是利用多態方式調用createEntityManager()方法獲取來的
EntityManager em = emf.createEntityManager();
// 創建person實體類對象,下面的操作打家都很熟悉了,給屬性賦值,反過來想就是設置rowkey,列,值
Person p = new Person();
p.setId(2);
p.setName("cat");
p.setAge(20);
// persist方法就是傳入一個對象,他會判斷是否存在,如果存在拋異常,否則創建對象實例,也就是把值添加到hbase相應的表中
em.persist(p);
// 關閉實體管理接口
em.close();
}
//2,D 刪除
@Test
public void delete() {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mykundera");
EntityManager em = emf.createEntityManager();
Person p = new Person();
// 如果不指定屬性值,他會把這個整個實體上指定的列族數據全刪掉
// em.remove(p);
p.setId(2);
p.setAge(19);
p.setName("cat");
em.close();
}
//3,U 修改
@Test
public void update() {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mykundera");
EntityManager em = emf.createEntityManager();
Person p = new Person();
p.setId(2);
p.setAge(50);
// 必須指定rowkey和列,調用merge("實體類.class")
em.merge(Person.class);
em.close();
}
//4,R 查
@Test
public void select() {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mykundera");
EntityManager em = emf.createEntityManager();
// 調用find(實體類.class,rowkey)方法返回實體類對象,直接打印對象就行了,因爲我重寫過toString了
Person p = em.find(Person.class, 1);
System.out.println(p);
em.close();
}
//使用querysql方式高級查詢,面向對象的sql語言
@Test
public void selectSql() {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mykundera");
EntityManager cm = emf.createEntityManager();
// 調用createQuery("面向對象的sql表名是對象名,列是屬性")
Query query = cm.createQuery("select p from Person p where p.id=2");
List<Person> re = query.getResultList();
for (Person person : re) {
System.out.println(person);
}
}
//使用過濾器實現querysql
@Test
public void filterselectSql() {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mykundera");
EntityManager cm = emf.createEntityManager();
// 使用filter過濾器步驟
// 1。創建EntityManager實例並使用getDelegate方法來獲取客戶機的映射
Map<String, Client> clli = (Map<String, Client>) cm.getDelegate();
// 2。從hbase持久化單元的客戶機映射中獲取客戶機。
HBaseClient hc = (HBaseClient) clli.get("mykundera");
// 3。最後,想要使用的任何過濾器創建一個過濾器對象,並將其設置爲HbaseClient對象:
// 值過濾,使用小於比較符,使用的字節比較器過濾小於2下的數據,之前博客有專門講過過濾器使用
ValueFilter vf = new ValueFilter(CompareOp.LESS, new BinaryComparator(Bytes.toBytes("10")));
hc.setFilter(vf);
Query query = cm.createQuery("select p from Person p ");
List<Person> re = query.getResultList();
for (Person person : re) {
System.out.println(person);
}
}
1.7 Hbase行鍵列族的概念,物理模型,表的設計原則?
行鍵:是hbase表自帶的,每個行鍵對應一條數據。
列族:是創建表時指定的,爲列的集合,每個列族作爲一個文件單獨存儲,存儲的數據都是字節數組,其中數據可以有很多,通過時間戳來區分。
物理模型:整個hbase表會拆分成多個region,每個region記錄着行鍵的起始點保存在不同的節點上,查詢時就是對各個節點的並行查詢,
當region很大時使用.META表存儲各個region的起始點,-ROOT又可以存儲.META的起始點。
Rowkey的設計原則:各個列族數據平衡,長度原則、相鄰原則,創建表的時候設置表放入regionserver緩存中,避免自動增長和時間,
使用字節數組代替string,最大長度64kb,最好16字節以內,按天分表,兩個字節散列,四個字節存儲時分毫秒。
列族的設計原則:儘可能少(按照列族進行存儲,按照region進行讀取,不必要的io操作),經常和不經常使用的兩類數據放入不同列族中,列族名字儘可能短。