第12章 關於Avro
Apache Avro 獨立於編程語言的數據序列化系統,支持壓縮、可切分
意在解決Hadoop中Writable類型的不足:缺乏語言的可移植性
Avro模式
通常用json編寫
Avro有豐富的模式解析能力,讀數據所用的模式不必與寫數據所用的模式相同
Avro定義了少量的基本數據類型,通過編寫模式的方式,可以被用於構建應用特定的數據結構
- 基本類型:null / boolean / int / long / float / double / bytes / string
- 複雜類型:array / map / record / enum / fixed / union
Avro序列化:
(1) 創建Avro模式
StringPair.avsc
{
"type":"record",
"name":"StringPair",
"doc":"A pair of strings",
"fields":[
{"name":"left","type":"string"},
{"name":"right","type":"string"}
]
}
(2) 加載模式
Schema.Parser parser = new Schema.Parser();
Schema schema = parser.parse(getClass().getResourceAsStream("StringPair.avsc"))
(3) 創建實例
GenericRecord datum = new GenericData.Record(schema); //可以用StringPair代替
datum.put("left","L");
datum.put("right","R");
(4) 輸出到流
ByteArrayOutputStream out = new ByteArrayOutputStream();
DatuWriter<GenericRecord> writer = new GenericDatumWriter<GenericRecord>(schema);
Encoder encoder = EncoderFactory.get().binaryEncoder(out,null);
writer.write(datum,encoder);
encoder.flush();
out.close();
(5) 反向讀回對象
DatumReader<GenericRecord> reader = new GenericDatumReader<GenericRecord>(schema);
Decoder decoder = DecoderFactory.get().binaryDecoder(out.toByteArray(),null);
GenericRecord result = reader.read(null,decoder);
assertThat(result.get("left").toString(),is("L"));
assertThat(result.get("right").toString(),is("R"));
Avro容器:
Avro容器主要存儲Avro對象序列,包括:Avro模式 + sync marker + 序列化對象數據塊
Avro格式打印:
java -jar $AVRO_HOME/avro-tools-*.jar tojson pairs.avro
第13章 關於Parquet
列式存儲格式,parquet能夠以真正的列式存儲格式來保存具有深度嵌套結構的數據
parquet原子類型:boolean int32 int64 int96 float double binary fixed_len_byte_array
parquet邏輯類型:UTF8 ENUM DECIMAL DATE LIST MAP
舉例:
LIST
message m {
required group a(LIST){
repeated group list{
required int32 element;}
}
}
parquet利用group類型構造複雜的類型,沒有註釋的group就是一個簡單的嵌套記錄
parquet使用Dremel編碼對任意一列數據的讀取都不需要涉及到其他列
parquet由 文件頭 + 多個文件塊 + 文件尾(版本信息、模式信息、鍵值、元數據信息)組成
parquet讀數據過程:
由於元數據保存在文件尾中,因此在讀parquet文件時,首先要找到文件的結尾,然後讀取文件尾中的元數據長度,並根據元數據長度逆向讀取文件尾中的元數據
parquet不需要sync marker,因爲文件塊之間的邊界信息被保存在文件尾的元數據中,因此parquet文件是可分割且可並行處理的。
Header Block Block Block Block Footer 行組 每個文件負責一個行組
|
Row group Column chunck Column chunck 行組 由 列塊構成,且一個列存儲一列數據
|
Page Page Page 頁 默認1MB 數據以頁爲單位存儲
頁是parquet文件的最小存儲單元,要讀取任意一行數據,就必須對這一行數據的頁進行解壓縮和解編碼處理。對於單行查找來說,頁越小,在找到目標值之前需要讀取的值就也少,效率越高
parquet自帶的編碼方式:
差分編碼、遊程長度編碼、字典編碼
Parquet會根據列的類型自動選擇合適的編碼方式,對於嵌套數據來說,每一頁還需要存儲該頁所包含的列定義深度和列元素重複次數。
parquet的文件塊大小不能超過其HDFS塊的大小
Parquet文件的讀寫:
(1) Parquet文件
MessageType schema = MessageTypeParser.parseMessageType(
"message Pair{\n" + "required binary left (UTF8); \n"+
"required binary right (UTF8); \n"+
"}");
(2) 構造Message
GroupFactory groupFactory = new SimpleGroupFactory(schema);
Group group = groupFactory.newGroup().append("left","L").append("right","R");
(3) 寫入 WriteSupport
Configuration conf = new Configuration();
Path path = new Path("data.parquet");
GroupWriteSupport writeSupport = new GroupWriteSupport();
GroupWriteSupport.setSchema(schema,conf);
ParquetWriter<Group> writer = new ParquetWriter<Group>(path,writeSupport,
ParquetWriter.DEFAULT_COMPRESSION_CODEC_NAME,
ParquetWriter.DEFAULT_BOLOCK_SIZE,
ParquetWriter.DEFAULT_PAGE_SIZE,
ParquetWriter.DEFAULT_PAGE_SIZE,
ParquetWriter.DEFAULT_IS_DECTIONARY_ENABLED,
ParquetWriter.DEFAULT_IS_VALIDATING_ENABLED,
ParquetWriter.WriterVersion.PARQEUT_1_0,conf
)
writer.write(group);
writer.close();
(4) 讀取 ReadSupport
GroupReadSupport readSupport = new GroupReadSupport();
ParquetReader<Group> reader = new ParquetReader<Group>(path,readSupport);
Group result = reader.read();
assertThat(result.getString("left",0),is("L"));
AvroParquetWriter、ProtoParquetWriter、ThriftParquetWriter
第14章 關於Flume
source --- channel --- sink
flume-ng agent \
--conf-file spool-to-logger.properties \ 屬性配置
--name agent1
--conf $FLUME_HOME/conf \ 通用配置
-Dflume.root.logger=INFO,console
一旦事務中的所有事件全部傳遞到channel且提交成功,那麼source就將該文件標註爲完成
如果channel到sink無法記錄,事務會回滾,而所有的事件仍保留在channel中
寫文件的結束條件:
- 超過給定的打開時間
- 達到給定的文件大小
- 寫滿了給定數量的事件
分區存儲方式:agent1.sinks.sink1.hdfs.path=xxx/year=%Y/month=%m/day=%d
一個Flume事件被寫入到哪個分區是由事件的header中的timestamp決定的,默認情況下沒有時間戳,它通過Flume的攔截器添加
爲source1增加一個時間戳攔截器,將source產生的每個時間添加一個timestamp header
agent1.sources.source1.interceptors = interceptor1
agent1.sources.source1.interceptors.interceptor1.type = timestamp
扇出:從一個source向多個channel,多個sink傳遞事件
agent1.sources = source1
agent1.sinks = sink1a sink1b
agent1.channels = channel1a channel1b
agent1.sources.source1.channels = channel1a channel1b
agent1.sinks.sink1a.channel = channel1a
agent1.sinks.sink1b.channel = channel1b
agent1.sources.source1.type = spooldir
agent1.sources.source1.spoolDir = /tmp/spooldir
agent1.sinks.sink1a.type = hdfs
agent1.sinks.sink1a.hdfs.path = /tmp/flume
agent1.sinks.sink1a.hdfs.filePrefix = events-%{host}
agent1.sinks.sink1a.hdfs.fileSuffix = .log
agent1.sinks.sink1a.hdfs.fileType = DataStream
agent1.sinks.sink1b.type = logger
agent1.channels.channel1a.type = file
agent1.channels.channel1b.type = memory
複用選擇器:某些事件發送到特定channel
Flume事件匯聚、Flume代理:p388
事務、交付保證:(1)多個類似的agent代理,防止agent掛掉 (2)多個類似的sink組
Sink組:多個sink當作一個sink處理,以實現故障轉移和負載均衡
如果某個sink不可用,就嘗試下一個sink,如果都不可用,事件也不會從channel中刪除
processor.backoff參數:防止重複連接sink組中的故障sink
Flume掌握:source、channel、sink、interceptor
p396:source、channel、sink、interceptor基本類別
第15章 關於Sqoop
Apache Sqoop是一個開源工具,允許用戶將數據從結構化存儲器抽取到Hadoop中,用於進一步處理
Sqoop1 與 Sqoop2:
Sqoop2對Sqoop進行了重寫,以解決Sqoop1架構上的侷限性
- Sqoop1是命令行工具,不提供Java api,編寫新的連接器需要做大量的工作
- Sqoop2具有以運行作業的服務器組件和一整套客戶端,包括命令行接口、用戶界面、REST API、Java api,Sqoop2的CLI與Sqoop1的CLI並不兼容
%sqoop
%sqoop help
%sqoop help import
Sqoop連接器:一個Sqoop連接器就是這個框架下的一個模塊化組件,用於支持Sqoop的導入和導出操作。包括:mysql、postgresql、oracle、Sql Server、DB2、Netezza
導入數據:
sqoop import --connect jdbc:mysql://localhost/hadoopguide --table widgets -m 1
默認情況下,該作業會並行使用4個map任務來加速導入過程
生成代碼:
sqoop codegen --connect jdbc:mysql://localhost/hadoopguide --table widgets --class-name Wight
- Sqoop查詢是根據劃分列:--split
- Sqoop導入控制:--where --query
- 定時運行導入:只有當特定列(--check-column)的值大於指定值(--last-value)時,Sqoop纔會導入數據
- 直接模式導入:--direct (比基於JDBC的導入更加高效)
Sqoop導入到Hive
Sqoop根據關係源數據中的表來生成一個Hive表
sqoop create-hive-table --connect jdbc:mysql://localhost/hadoopguide \
--table wights --fields-terminated-by ',' -m 1 --hive-import
Sqoop保存大對象
大對象通常保存在CLOB或BLOB類型的列中,一般大對象和他們的行分開存儲,在訪問大對象時,需要通過行中包含的引用來打開它
Sqoop導出
sqoop export --connect jdbc:mysql://localhost/hadoopguide -m 1 \
--table sales_by_zip --export-dir /usr/hive/warehouse/zip_profits \
--input-fileds-terminated-by '\0001'
Sqoop導出過程:
Sqoop將數據導出到一個臨時階段表中,然後在導出任務完成前,在一個事務中將臨時階段表中的數據全部轉移到目標表中
第17章 關於Hive
Hive執行引擎:mapreduce、tez、spark
在使用mapreduce時,中間作業的輸出會被“物化”存儲在hdfs上,tez和spark則不同,會根據Hive規劃器的請求,把中間結果寫到本地磁盤上,甚至在內村中緩存,以避免額外的複製開銷
寫時模式:傳統數據庫在寫入時檢查,寫時模式有利於提高查詢性能
讀時模式:Hive在讀數據時檢查,不需要解析,數據的加載僅僅是文件複製或移動
Hdfs不提供就地文件更新,因此插入、更新和刪除操作引起的一切變化都會保存在一個較小的增量文件中,由metastore在後臺運行的mapreduce作業會定期將這些增量合併到“基表”文件中
事務:Hive在0.13.0引入事務
鎖:Hive在0.7.0版本引入了表級和分區級的鎖,鎖由zookeeper透明管理
Hive索引:緊湊索引、位圖索引。緊湊索引存儲每個值的hdfs塊號,位圖索引使用壓縮的位集合來高效存儲具有某個特殊值的行
其他SQL-on-Hadoop:Impala、Presto、Spark SQL、Phoenix(SQL on HBase)
Hive的數據類型:boolean、tinyint、samllint、int、bigint、float、double、decimal、string、varchar、char、binary、timestamp、date、array、map、struct、union
- TINYINT:1字節有符號整數,-128 - 127,對應java byte
- SMALLINT:2字節有符號整數,-32768 - 32767,對應java short
- INT:4字節有符號整數,-2147483648 - 2147483647,對應java int
- BIGINT:8字節有符號整數,對應java long
- FLOAT:4字節單精度浮點數
- DOUBLE:8字節雙精度浮點數
- DECIMAL:任意精度有符號小數
- STRING:無上限可變長度字符串
- VARCHAR:可變長度字符串
- CHAR:固定長度字符串
- ARRAY:例:array(1,2)
- Map:例:map('a',1,'b',2)
- STRUCT:struct(‘a’,1,1.0)
- UNION:create_union(1,'a',63)
Hive隱式類型轉換:任何數值類型都可以隱式轉換爲一個範圍更廣的類型或者文本類型(STRING VARCHAR CHAR)
Hive強制類型轉換: cast('xxx' as int) 轉換失敗會返回null
查看分區:show partitions xxx
存儲格式:
- 默認的行內分隔符不是製表符,而是ASCII控制碼集合中的Control-A
- 集合類元素的默認分隔符爲字符Control-B
導入數據:CATS:create table ...... as select
多表插入:
from source
insert overwrite table xxx select col1,col2
insert overwirte table yyy select col3
sort by < order by < distribute by < cluster by
- sort by : 爲每個reducer排序
- order by :全排序
- distribute by :分配同一個reducer之中
視圖:視圖是隻讀的,無法通過視圖爲基表加載或插入數據
- UDF:一個輸入行,一個輸出行
- UDAF:多個輸入行,一個輸出行
- UDTF:一個輸入行,多個輸出行
第19章 關於Spark
DAG引擎:Spark作業是由任意的多階段有向無環圖(DAG)構成
job -----> 多個stage ----->運行在RDD分區上
RDD:彈性分佈式數據集,在集羣中跨多個機器分區存儲的一個只讀的對象集合
加載或執行轉換並不會立即觸發任何數據處理的操作,只不過創建了一個計算的計劃,只有當RDD執行某個動作時,纔會觸發真正的計算
RDD的創建:
- 來自內存中的對象集合: sc.parallelize(1 to 10,10)
- 使用外部存儲器中的數據集(如HDFS): RDD[String] = sc.textFile(inputPath)
- 對現有RDD進行轉換
RDD的轉換:(1)轉換 (2)動作
如果返回類型是一個RDD,則爲轉換,否則爲動作
- reduceByKey() 不需要初始值
val pairs:RDD[(String,Int)] = sc.parallelize(Array(("a",3)))
val sums:RDD[(String,Int)] = pairs.reduceByKey(_+_)
- foldByKey()需要初始值
val sums:RDD[(String,Int)] = pairs.foldByKey(0)(_+_)
- aggregateByKey
val sets:RDD[(String,HashSet[Int])] = pairs.aggregateByKey(new HashSet[Int])(_+=_,_++=_)
第一個函數負責把Int合併到HashSet[Int]中
第二個函數負責合併兩個HashSet[Int]中的值
例如:Array((“a”,3),(“a”,1),(“b”,7),("a",5))
結果爲:((“a”,Set(1,3,5)),("b",7))
持久化:cache()並不會立即緩存RDD,它用一個標誌來對RDD進行標記,以指示RDD應當在Spark作業運行時被緩存
持久化級別:MEMORY_ONLY MEMEORY_ONLY_SER
序列化:
Kryo序列化: conf.set("spark.serializer","org.apache.spark.serializer.KryoSerializer")
廣播變量:
在經過序列化後發送給各個executor,然後緩存在那裏,以便後期任務可以在需要時訪問它
val lookup:Broadcast[Map[Int,String]] = sc.broadcast(Map(1 ->"a",2->"e",3->"i",4->"o"))
val result = sc.parallelize(Array(2,1,3)).map(lookup.value(_))
累加器:只做加法的累加器,類似於MapReduce中的計數器
val count:Accumulator[Int] = sc.accumulator(0)
val result = sc.parallelize(Array(1,2,3)).map(i => {count +=1; i}}) .reduce((x,y) => x+y)
Spark program
|
SparkContext
|
DAGScheduler
|
TaskScheduler
|
SchedulerBackend----------->ExecutorBackend
|
dirver Executor
|
ShuffleMapTask
executor
dirver:負責託管應用並作爲作業調度任務
executor:專屬應用
兩種類型的任務:shuffle map任務 、 result任務
- shuffle map任務運行在除最終階段之外的其他所有階段中,返回一些可以讓下一階段檢索其分區的信息
- result任務運行在最終階段,每個result任務在它自己的RDD分區上計算,並把結果發送給driver,driver進行彙總
執行管理模式:
- 本地模式:有一個executor和dirver運行在同一個JVM中
local 、local[n]、local(*) (機器的每一個內核一個線程)
- 獨立模式
- Mesos模式
- Yarn模式:yarn-client 、 yarn-cluster
yarn是唯一一個能夠與Hadoop的Kerberos集成的管理器
yarn-client 客戶端模式的driver在客戶端運行
yarn-cluster 集羣模式的driver在yarn的application master上運行
第20章 關於HBase
面向列的分佈式數據庫,可以實時隨機訪問超大規模數據集
基本概念:
- 行
- 行鍵
- 列族 每個列都具有相同的前綴 列族:修飾符
一個表的列族要提前給出,列可以在更新中提供。只要列族存在,列可以隨時加進去
Master ---------------------------> ZooKeeper Cluster 【hbase:meta 目標主控機等信息】
|
Regionserver
|
HDFS
hbase:meta
特殊目錄表,維護着當前集羣上所有區域的列表、狀態和位置
hbase:meta表中的項使用區域名作爲鍵(有序),區域名由所屬的表名、區域的起始行、區域的創建時間以及MD5哈希值組成
例如:TestTable,xyz,12379989859898.1sds123ffdf
區域變化:分裂、禁用、啓用等操作,目錄表會進行相應的更新
客戶端通過hbase:meta查找到所有節點及其位置,然後直接連接regionserver進行交互
客戶端會對hbase:meta的內容進行緩存,一旦發生錯誤,再去查看hbase:meta獲取新位置
- “重做”日誌內容
- 文件壓縮
- 文件分割
hbase shell
create 'test','data'
list
scan 'test'
put 'test','row1','data:1','value1'
get 'test','row1'
disable 'test'
drop 'test'
表的離線:disable
表的在線:enable
HBase特性:
- 沒有真正的索引
- 自動分區
- 線性擴展和新節點的自動管理
- 普通商用硬件支持
- 容錯
- 批處理
HBase使用HDFS的方式 與 Mapreduce使用HDFS方式 區別:
- Mapreduce首先打開HDFS文件,然後map任務流失處理文件內容,最後關閉文件
- HBase中,數據文件在啓動時就被打開,並在處理過程中始終保持打開的狀態
第21章 關於ZooKeeper
ZooKeeper是Hadoop分佈式協調服務,不能避免分佈式出現部分失敗
ZooKeeper具有以下特點:
- 簡單、精簡
- 可以實現多種協調數據結構和協議:分佈式隊列、分佈式鎖、領導者選舉
- 避免系統出現單點故障,構建可靠應用程序
- 松耦合交互方式
- 提供了通用協調模式實現方法的開源共享庫
znode:既可以保存數據的容器,也可以保存其他znode容器
創建znode的參數:路徑、znode內容、控制列表、znode類型
//ZooKeeper創建組
public class CreateGroup imploements Wather{
private static final int SESSION_TIMEOUT = 5000;
private ZooKeeper zk;
private CountDownLatch connectedSignal = new CountDownLatch(1);
public void connect(String hosts) throws Exception{
zk = new ZooKeeper(hosts,SESSION_TEIMOUT,this);
//主機地址、會話超時參數、Watcher實例
connectedSignal.await();
}
@Override
public void process(WatchedEvent event){
if(event.getState()== KeeperState.SyncConnected){
connectedSignal.countDown();
}
}
public void create(String groupName) throws Exception{
String path = "/" + groupName;
String createdPath = zk.create(path,null,Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
}
public void close() throws Exception{
zk.close();
}
}
Znode類型:
- 短暫的:短暫znode會被zk服務刪除,依賴客戶端會話
- 持久的:不依賴會話
列出列表:zkCli.sh -server locahost ls /zoo
刪除組:
節點路徑、版本號
如果提供的版本號一致,ZooKeeper會刪除這個znode
如果版本號設置成-1,可以繞過版本檢測機制,不管znode版本號都會刪除
zk的目的:
ZooKeeper用來協調服務,不是用來大容量數據存儲,因此一個znode能存儲的數據限制在1MB以內。且zk訪問具有原子性,zk允許客戶端讀到的數據滯後於zookeeper服務的最新狀態
zk順序號:
如果創建znode時設置了順序標誌,在創建znode會附加一個值,這個值由一個單調遞增的計數器添加
在一個分佈式系統中,順序號可以被哦用於爲所有的事件進行全局排序,這樣客戶端就可以通過順序號來推斷事件的順序
觀察、觸發事件
exsits、getData、getChildren
基本操作
create、delete、exists、getACL、setACL、getChildren、getData、setData、sync
quota:用來設置某個節點的子字節數和本身的數據長度:setquota /xx -n number -b byte
ACL列表:
ACL依賴於ZooKeeper的客戶端身份驗證機制
- digest 通過用戶名和密碼識別客戶端
- sasl 通過Kerberos來識別客戶端
- ip 通過客戶端的ip地址來識別客戶端
ZooKeeper運行方式:
- 獨立模式:只有一個ZooKeeper服務器
- 複製模式:ZooKeeper通過複製模式來實現高可用性,只要集羣中半數以上的機器處於可用狀態,就能夠提供服務。確保對znode樹上的每一個修改都會被複制到集羣中超過半數的機器上
ZooKeeper使用了Zab協議(非Paxos,依靠TCP保證消息順序)
- 領導者選舉:一旦半數的跟隨者已經將其狀態與領導者同步,則表明這個階段已完成
- 原子廣播:所有的寫請求都會轉發給領導者,再由領導者將更新廣播給跟隨者,當半數跟隨者已經將修改持久化後,領導者纔會提交這個更新,客戶端纔會收到更新成功的響應
如果領導者出現故障,其餘的機器會選出另外一個領導者,並和新的領導者一起繼續提供服務。如果之前的領導者恢復正常,則會成爲更隨者
領導者負責提供寫請求、跟隨者負責響應讀請求
一致性:
- 順序一致性:對znode的更新都賦予了全局ID。會按照發送順序被提交
- 原子性
- 單一系統映象:一個客戶端無論連接那一臺服務器,都將看到同樣的系統視圖
- 持久性
- 及時性:任何客戶端所看到的滯後系統視圖都是有限的,在讀數據之前調用sync
Zookeeper“滴答”:
tick time:會好超時參數的值不可以小於2個滴答其不可以大於20個滴答
一般zk的服務器越多,會話超時的設置應該越大;連接超時、讀超時、ping設置越小
Zookeeper狀態轉移:
Connecting --- > Connected ---> Closed
Zookeeper兩種異常:
- InterruptedException:interrupt 連接異常
- KeeperException:通信問題
分佈式鎖:可用於大型分佈式系統中實現領導者選舉
分佈式鎖能夠在一組進程之間提供互斥機制,使得任何時刻只有一個進程可以持有鎖
實現原理:
指定一個鎖的znode
希望獲得鎖的客戶端創建一些短暫順序的znode作爲znode的子節點,爲競爭者進行強制排序
在任何時間點,順序號最小的客戶端將持有鎖
例如:/leader節點下,/leader/lock-1 和 /leader/lock-2 lock-1會持有鎖
釋放鎖:
刪除節點即可
分佈式鎖造成的問題:
- 羊羣效應:每次鎖釋放或一個新進程開始申請鎖的時候,觀察者會被觸發通知每一個客戶端, 而只有一個客戶端獲得鎖,這個過程會產生峯值流量,對zk造成壓力
解決方法:當前子節點消失時通知下一個客戶端
2. 可恢復異常:連接丟失導致操作失敗,重新連接會造成刪不掉的孤兒znode,導致死鎖
解決方法:znode名稱嵌入一個ID,如果客戶端出現連接丟失,重新連接後可以對鎖節點的所有子節點進行檢查,查看是否包含ID,如果有,則知道自己創建成功,不需要再創建
zookeeper觀察節點:
沒有投票權的跟隨者,在不影響寫性能的情況下提高讀性能
myid:每個服務器的id
Zookeeper配置:
tickTime = 2000
dataDir=/disk1/zookeeper
dataLogDir=/disk2/zookeeper clientPort=2181 //客戶端端口
initLimit=5//跟隨者與領導者同步的時間範圍,半數以上的跟隨者需未完成,領導者將棄位,重新領導者選舉
syncLimit=2 //允許跟隨者與領導者同步的時間,未完成,跟隨者會重啓
server.1=zookeeper1:2888:3888 //2888:跟隨者端口
server.2=zookeeper1:2888:3888 //3888:領導者選舉階段與其他服務器連接端口
server.3=zookeeper1:2888:3888