漫談數(shù)據(jù)倉(cāng)庫(kù)之拉鏈表(原理榕吼、設(shè)計(jì)以及在Hive中的實(shí)現(xiàn)) - 簡(jiǎn)書(shū) http://www.reibang.com/p/799252156379
0x00 前言
本文將會(huì)談一談在數(shù)據(jù)倉(cāng)庫(kù)中拉鏈表相關(guān)的內(nèi)容继低,包括它的原理竹宋、設(shè)計(jì)劳澄、以及在我們大數(shù)據(jù)場(chǎng)景下的實(shí)現(xiàn)方式。
全文由下面幾個(gè)部分組成:
先分享一下拉鏈表的用途蜈七、什么是拉鏈表秒拔。
通過(guò)一些小的使用場(chǎng)景來(lái)對(duì)拉鏈表做近一步的闡釋?zhuān)约袄湵砗统S玫那衅淼膮^(qū)別。
舉一個(gè)具體的應(yīng)用場(chǎng)景飒硅,來(lái)設(shè)計(jì)并實(shí)現(xiàn)一份拉鏈表砂缩,最后并通過(guò)一些例子說(shuō)明如何使用我們?cè)O(shè)計(jì)的這張表(因?yàn)楝F(xiàn)在Hive的大規(guī)模使用作谚,我們會(huì)以Hive場(chǎng)景下的設(shè)計(jì)為例)。
分析一下拉鏈表的優(yōu)缺點(diǎn)庵芭,并對(duì)前面的提到的一些內(nèi)容進(jìn)行補(bǔ)充說(shuō)明妹懒,比如說(shuō)拉鏈表和流水表的區(qū)別。
0x01 什么是拉鏈表
拉鏈表是針對(duì)數(shù)據(jù)倉(cāng)庫(kù)設(shè)計(jì)中表存儲(chǔ)數(shù)據(jù)的方式而定義的双吆,顧名思義眨唬,所謂拉鏈,就是記錄歷史伊诵。記錄一個(gè)事物從開(kāi)始单绑,一直到當(dāng)前狀態(tài)的所有變化的信息。
我們先看一個(gè)示例曹宴,這就是一張拉鏈表搂橙,存儲(chǔ)的是用戶(hù)的最基本信息以及每條記錄的生命周期。我們可以使用這張表拿到最新的當(dāng)天的最新數(shù)據(jù)以及之前的歷史數(shù)據(jù)笛坦。
注冊(cè)日期
用戶(hù)編號(hào)
手機(jī)號(hào)碼
t_start_date
t_end_date
2017-01-01
001
111111
2017-01-01
9999-12-31
2017-01-01
002
222222
2017-01-01
2017-01-01
2017-01-01
002
233333
2017-01-02
9999-12-31
2017-01-01
003
333333
2017-01-01
9999-12-31
2017-01-01
004
444444
2017-01-01
2017-01-01
2017-01-01
004
432432
2017-01-02
2017-01-02
2017-01-01
004
432432
2017-01-03
9999-12-31
2017-01-02
005
555555
2017-01-02
2017-01-02
2017-01-02
005
115115
2017-01-03
9999-12-31
2017-01-03
006
666666
2017-01-03
9999-12-31
我們暫且不對(duì)這張表做細(xì)致的講解区转,后文會(huì)專(zhuān)門(mén)來(lái)闡述怎么來(lái)設(shè)計(jì)、實(shí)現(xiàn)和使用它版扩。
拉鏈表的使用場(chǎng)景
在數(shù)據(jù)倉(cāng)庫(kù)的數(shù)據(jù)模型設(shè)計(jì)過(guò)程中废离,經(jīng)常會(huì)遇到下面這種表的設(shè)計(jì):
有一些表的數(shù)據(jù)量很大,比如一張用戶(hù)表礁芦,大約10億條記錄蜻韭,50個(gè)字段,這種表柿扣,即使使用ORC壓縮肖方,單張表的存儲(chǔ)也會(huì)超過(guò)100G,在HDFS使用雙備份或者三備份的話(huà)就更大一些未状。
表中的部分字段會(huì)被update更新操作俯画,如用戶(hù)聯(lián)系方式,產(chǎn)品的描述信息司草,訂單的狀態(tài)等等艰垂。
需要查看某一個(gè)時(shí)間點(diǎn)或者時(shí)間段的歷史快照信息,比如埋虹,查看某一個(gè)訂單在歷史某一個(gè)時(shí)間點(diǎn)的狀態(tài)猜憎。
表中的記錄變化的比例和頻率不是很大,比如搔课,總共有10億的用戶(hù)拉宗,每天新增和發(fā)生變化的有200萬(wàn)左右,變化的比例占的很小。
那么對(duì)于這種表我該如何設(shè)計(jì)呢旦事?下面有幾種方案可選:
方案一:每天只留最新的一份魁巩,比如我們每天用Sqoop抽取最新的一份全量數(shù)據(jù)到Hive中。
方案二:每天保留一份全量的切片數(shù)據(jù)姐浮。
方案三:使用拉鏈表谷遂。
為什么使用拉鏈表
現(xiàn)在我們對(duì)前面提到的三種進(jìn)行逐個(gè)的分析。
方案一
這種方案就不用多說(shuō)了卖鲤,實(shí)現(xiàn)起來(lái)很簡(jiǎn)單肾扰,每天drop掉前一天的數(shù)據(jù),重新抽一份最新的蛋逾。
優(yōu)點(diǎn)很明顯集晚,節(jié)省空間,一些普通的使用也很方便区匣,不用在選擇表的時(shí)候加一個(gè)時(shí)間分區(qū)什么的偷拔。
缺點(diǎn)同樣明顯,沒(méi)有歷史數(shù)據(jù)亏钩,先翻翻舊賬只能通過(guò)其它方式莲绰,比如從流水表里面抽。
方案二
每天一份全量的切片是一種比較穩(wěn)妥的方案姑丑,而且歷史數(shù)據(jù)也在蛤签。
缺點(diǎn)就是存儲(chǔ)空間占用量太大太大了,如果對(duì)這邊表每天都保留一份全量栅哀,那么每次全量中會(huì)保存很多不變的信息震肮,對(duì)存儲(chǔ)是極大的浪費(fèi),這點(diǎn)我感觸還是很深的......
當(dāng)然我們也可以做一些取舍留拾,比如只保留近一個(gè)月的數(shù)據(jù)戳晌?但是,需求是無(wú)恥的间驮,數(shù)據(jù)的生命周期不是我們能完全左右的。
拉鏈表
拉鏈表在使用上基本兼顧了我們的需求马昨。
首先它在空間上做了一個(gè)取舍竞帽,雖說(shuō)不像方案一那樣占用量那么小,但是它每日的增量可能只有方案二的千分之一甚至是萬(wàn)分之一鸿捧。
其實(shí)它能滿(mǎn)足方案二所能滿(mǎn)足的需求屹篓,既能獲取最新的數(shù)據(jù),也能添加篩選條件也獲取歷史的數(shù)據(jù)匙奴。
所以我們還是很有必要來(lái)使用拉鏈表的堆巧。
0x02 拉鏈表的設(shè)計(jì)和實(shí)現(xiàn)
如何設(shè)計(jì)一張拉鏈表
下面我們來(lái)舉個(gè)栗子詳細(xì)看一下拉鏈表。
我們接上在《漫談數(shù)據(jù)倉(cāng)庫(kù)之維度建模》中的電商網(wǎng)站的例子谍肤,現(xiàn)在以用戶(hù)的拉鏈表來(lái)說(shuō)明啦租。
我們先看一下在Mysql關(guān)系型數(shù)據(jù)庫(kù)里的user表中信息變化。
在2017-01-01這一天表中的數(shù)據(jù)是:
注冊(cè)日期
用戶(hù)編號(hào)
手機(jī)號(hào)碼
2017-01-01
001
111111
2017-01-01
002
222222
2017-01-01
003
333333
2017-01-01
004
444444
在2017-01-02這一天表中的數(shù)據(jù)是荒揣, 用戶(hù)002和004資料進(jìn)行了修改篷角,005是新增用戶(hù):
注冊(cè)日期
用戶(hù)編號(hào)
手機(jī)號(hào)碼
備注
2017-01-01
001
111111
2017-01-01
002
233333
(由222222變成233333)
2017-01-01
003
333333
2017-01-01
004
432432
(由444444變成432432)
2017-01-02
005
555555
(2017-01-02新增)
在2017-01-03這一天表中的數(shù)據(jù)是, 用戶(hù)004和005資料進(jìn)行了修改系任,006是新增用戶(hù):
注冊(cè)日期
用戶(hù)編號(hào)
手機(jī)號(hào)碼
備注
2017-01-01
001
111111
2017-01-01
002
233333
2017-01-01
003
333333
2017-01-01
004
654321
(由432432變成654321)
2017-01-02
005
115115
(由555555變成115115)
2017-01-03
006
666666
(2017-01-03新增)
如果在數(shù)據(jù)倉(cāng)庫(kù)中設(shè)計(jì)成歷史拉鏈表保存該表恳蹲,則會(huì)有下面這樣一張表,這是最新一天(即2017-01-03)的數(shù)據(jù):
注冊(cè)日期
用戶(hù)編號(hào)
手機(jī)號(hào)碼
t_start_date
t_end_date
2017-01-01
001
111111
2017-01-01
9999-12-31
2017-01-01
002
222222
2017-01-01
2017-01-01
2017-01-01
002
233333
2017-01-02
9999-12-31
2017-01-01
003
333333
2017-01-01
9999-12-31
2017-01-01
004
444444
2017-01-01
2017-01-01
2017-01-01
004
432432
2017-01-02
2017-01-02
2017-01-01
004
654321
2017-01-03
9999-12-31
2017-01-02
005
555555
2017-01-02
2017-01-02
2017-01-02
005
115115
2017-01-03
9999-12-31
2017-01-03
006
666666
2017-01-03
9999-12-31
說(shuō)明
t_start_date表示該條記錄的生命周期開(kāi)始時(shí)間俩滥,t_end_date表示該條記錄的生命周期結(jié)束時(shí)間嘉蕾。
t_end_date = '9999-12-31'表示該條記錄目前處于有效狀態(tài)。
如果查詢(xún)當(dāng)前所有有效的記錄霜旧,則select * from user where t_end_date = '9999-12-31'错忱。
如果查詢(xún)2017-01-02的歷史快照,則select from user where t_start_date <= '2017-01-02' and t_end_date >= '2017-01-02'颁糟。(此處要好好理解航背,是拉鏈表比較重要的一塊。*)
在Hive中實(shí)現(xiàn)拉鏈表
在現(xiàn)在的大數(shù)據(jù)場(chǎng)景下棱貌,大部分的公司都會(huì)選擇以Hdfs和Hive為主的數(shù)據(jù)倉(cāng)庫(kù)架構(gòu)玖媚。目前的Hdfs版本來(lái)講,其文件系統(tǒng)中的文件是不能做改變的婚脱,也就是說(shuō)Hive的表智能進(jìn)行刪除和添加操作今魔,而不能進(jìn)行update≌厦常基于這個(gè)前提错森,我們來(lái)實(shí)現(xiàn)拉鏈表。
還是以上面的用戶(hù)表為例篮洁,我們要實(shí)現(xiàn)用戶(hù)的拉鏈表涩维。在實(shí)現(xiàn)它之前,我們需要先確定一下我們有哪些數(shù)據(jù)源可以用袁波。
我們需要一張ODS層的用戶(hù)全量表瓦阐。至少需要用它來(lái)初始化。
每日的用戶(hù)更新表篷牌。
而且我們要確定拉鏈表的時(shí)間粒度睡蟋,比如說(shuō)拉鏈表每天只取一個(gè)狀態(tài),也就是說(shuō)如果一天有3個(gè)狀態(tài)變更枷颊,我們只取最后一個(gè)狀態(tài)戳杀,這種天粒度的表其實(shí)已經(jīng)能解決大部分的問(wèn)題了该面。
另外,補(bǔ)充一下每日的用戶(hù)更新表該怎么獲取信卡,據(jù)筆者的經(jīng)驗(yàn)隔缀,有3種方式拿到或者間接拿到每日的用戶(hù)增量,因?yàn)樗容^重要坐求,所以詳細(xì)說(shuō)明:
我們可以監(jiān)聽(tīng)Mysql數(shù)據(jù)的變化蚕泽,比如說(shuō)用Canal,最后合并每日的變化桥嗤,獲取到最后的一個(gè)狀態(tài)须妻。
假設(shè)我們每天都會(huì)獲得一份切片數(shù)據(jù),我們可以通過(guò)取兩天切片數(shù)據(jù)的不同來(lái)作為每日更新表泛领,這種情況下我們可以對(duì)所有的字段先進(jìn)行concat荒吏,再取md5,這樣就ok了渊鞋。
流水表绰更!有每日的變更流水表。
ods層的user表
現(xiàn)在我們來(lái)看一下我們ods層的用戶(hù)資料切片表的結(jié)構(gòu):
CREATE EXTERNAL TABLE ods.user ( user_num STRING COMMENT '用戶(hù)編號(hào)', mobile STRING COMMENT '手機(jī)號(hào)碼', reg_date STRING COMMENT '注冊(cè)日期'COMMENT '用戶(hù)資料表'PARTITIONED BY (dt string)ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'STORED AS ORCLOCATION '/ods/user';)
ods層的user_update表
然后我們還需要一張用戶(hù)每日更新表锡宋,前面已經(jīng)分析過(guò)該如果得到這張表儡湾,現(xiàn)在我們假設(shè)它已經(jīng)存在。
CREATE EXTERNAL TABLE ods.user_update ( user_num STRING COMMENT '用戶(hù)編號(hào)', mobile STRING COMMENT '手機(jī)號(hào)碼', reg_date STRING COMMENT '注冊(cè)日期'COMMENT '每日用戶(hù)資料更新表'PARTITIONED BY (dt string)ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'STORED AS ORCLOCATION '/ods/user_update';)
拉鏈表
現(xiàn)在我們創(chuàng)建一張拉鏈表:
CREATE EXTERNAL TABLE dws.user_his ( user_num STRING COMMENT '用戶(hù)編號(hào)', mobile STRING COMMENT '手機(jī)號(hào)碼', reg_date STRING COMMENT '用戶(hù)編號(hào)', t_start_date , t_end_dateCOMMENT '用戶(hù)資料拉鏈表'ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LINES TERMINATED BY '\n'STORED AS ORCLOCATION '/dws/user_his';)
實(shí)現(xiàn)sql語(yǔ)句
然后初始化的sql就不寫(xiě)了执俩,其實(shí)就相當(dāng)于是拿一天的ods層用戶(hù)表過(guò)來(lái)就行徐钠,我們寫(xiě)一下每日的更新語(yǔ)句。
現(xiàn)在我們假設(shè)我們已經(jīng)已經(jīng)初始化了2017-01-01的日期役首,然后需要更新2017-01-02那一天的數(shù)據(jù)尝丐,我們有了下面的Sql。
然后把兩個(gè)日期設(shè)置為變量就可以了衡奥。
INSERT OVERWRITE TABLE dws.user_hisSELECT * FROM( SELECT A.user_num, A.mobile, A.reg_date, A.t_start_time, CASE WHEN A.t_end_time = '9999-12-31' AND B.user_num IS NOT NULL THEN '2017-01-01' ELSE A.t_end_time END AS t_end_time FROM dws.user_his AS A LEFT JOIN ods.user_update AS B ON A.user_num = B.user_numUNION SELECT C.user_num, C.mobile, C.reg_date, '2017-01-02' AS t_start_time, '9999-12-31' AS t_end_time FROM ods.user_update AS C) AS T
0x03 補(bǔ)充
好了爹袁,我們分析了拉鏈表的原理、設(shè)計(jì)思路矮固、并且在Hive環(huán)境下實(shí)現(xiàn)了一份拉鏈表失息,下面對(duì)拉鏈表做一些小的補(bǔ)充。
拉鏈表和流水表
流水表存放的是一個(gè)用戶(hù)的變更記錄档址,比如在一張流水表中盹兢,一天的數(shù)據(jù)中,會(huì)存放一個(gè)用戶(hù)的每條修改記錄辰晕,但是在拉鏈表中只有一條記錄蛤迎。
這是拉鏈表設(shè)計(jì)時(shí)需要注意的一個(gè)粒度問(wèn)題确虱。我們當(dāng)然也可以設(shè)置的粒度更小一些含友,一般按天就足夠。
查詢(xún)性能
拉鏈表當(dāng)然也會(huì)遇到查詢(xún)性能的問(wèn)題,比如說(shuō)我們存放了5年的拉鏈數(shù)據(jù)窘问,那么這張表勢(shì)必會(huì)比較大辆童,當(dāng)查詢(xún)的時(shí)候性能就比較低了,個(gè)人認(rèn)為兩個(gè)思路來(lái)解決:
在一些查詢(xún)引擎中惠赫,我們對(duì)start_date和end_date做索引把鉴,這樣能提高不少性能。
保留部分歷史數(shù)據(jù)儿咱,比如說(shuō)我們一張表里面存放全量的拉鏈表數(shù)據(jù)庭砍,然后再對(duì)外暴露一張只提供近3個(gè)月數(shù)據(jù)的拉鏈表。
0xFF 總結(jié)
我們?cè)谶@篇文章里面詳細(xì)地分享了一下和拉鏈表相關(guān)的知識(shí)點(diǎn)混埠,但是仍然會(huì)有一會(huì)遺漏怠缸。歡迎交流。
在后面的使用中又有了一些心得钳宪,補(bǔ)充進(jìn)來(lái):
使用拉鏈表的時(shí)候可以不加t_end_date揭北,即失效日期,但是加上之后吏颖,能優(yōu)化很多查詢(xún)搔体。
可以加上當(dāng)前行狀態(tài)標(biāo)識(shí),能快速定位到當(dāng)前狀態(tài)半醉。
在拉鏈表的設(shè)計(jì)中可以加一些內(nèi)容疚俱,因?yàn)槲覀兠刻毂4嬉粋€(gè)狀態(tài),如果我們?cè)谶@個(gè)狀態(tài)里面加一個(gè)字段奉呛,比如如當(dāng)天修改次數(shù)计螺,那么拉鏈表的作用就會(huì)更大。