1. 程式人生 > >基於大資料技術構建數倉模型實踐

基於大資料技術構建數倉模型實踐

        最近剛接觸一個線上執行的數倉環境,是針對使用者流量日誌做點選量指標的多維度分析,維度表每天一個快照,經過資料統計分析發現有的維度表資料量很大,每天竟然有5億多條的素材日誌,並且這些維度資料是漸變維度,資料儲存在亞馬遜S3檔案系統上面,嚴重浪費公司的儲存成本,同時要是查詢跨度一個周的資料則涉及到的維度資料就40億條進行關聯,這還不算其他維度的統計在內,個人觀點,涉及到這些大維度資料的統計應該通過當前構建的數倉應該沒有實際的應用價值。

多維設計的整體思路要簡化和加速查詢,不要求必須滿足3NF(消除資料冗餘,規範化程度越高,需要關聯的表也越多),也就是說維度模型允許可控的資料冗餘(資料更新異常,由於資料倉庫中的資料很少有更新,主要是查詢操作),減少表和表間關係的數量,從而提高查詢速度;例如,假設有100萬訂單,每個訂單有10條明細,訂單狀態和訂單明細狀態各有10種,如果使用者要查詢某種狀態特性的訂單(狀態是where過濾條件-->維度,即看問題的角度),按3NF模型,邏輯上需要關聯100萬與1000萬的兩個大表,然後過濾兩個表的狀態值得到所要的結果,其中100萬是訂單粒度,1000萬是訂單明細粒度;另一方面,事實表按最細資料粒度有1000萬記錄(訂單明細),3NF裡的訂單表屬性在事實表裡是冗餘資料,狀態維度有100條資料(10條訂單狀態 * 10條訂單明細狀態),只需要關聯1000萬與100的兩個表,再進行狀態過濾即可。

        通過對現存數倉的調研提出下面的解決方案。

        對維度表的處理:一、為提高查詢效能,會優化表的儲存,主要體現在1)儲存型別採用壓縮率比較高的ORC,降低公司儲存成本,更重要的是該儲存格式支援hive進行行級更新;( ./hive –orcfiledump -j -p /hivedata/warehouse2/lxw1234_orc1/000000_0)2)通過水平拆分技術,採用分割槽+分桶,這些手段主要為減少掃描的資料量,分桶會根據下面的多維模型進行詳細介紹;3)垂直拆分,因為有的表字段變化比較慢,有的表變化比較快,所以要對這些不同型別的欄位區別對待,通過對各個維度表的資料統計分析,大部分欄位變化的都很慢,憑藉經驗而談,只有變化較慢的欄位採用統計的價值,實際上90%以上的統計應該都落到變化較慢的欄位上面,對應變化較慢的欄位採用SCD2技術實現拉鍊表。二、為提供更豐富的查詢型別,從事實表中抽取出維度資料,例如作業系統維度和裝置品牌維度。

        最終設計出的多維資料模型如下所示:


                                                    圖 1

        上面構建的多維資料模型中的維度表字段沒有全列出來,因為公司因為所限,但是不影響技術的分享;經過對維度表的分析,主要有三張維度表的資料量特別大,對應的資料量如下表所示:

維度名稱

總條數

時間跨度(天)

平均每天的記錄數

拉鍊表之後總條數

維度型別

備註

dim_adn_creative_list

123926985436

205

604521880

610904029

大維表

dim_adn_creative_source

38318221849

205

186918155

721969702

大維表

utime(更新時間戳) 和status(狀態)兩個欄位變化相對較大

dim_adn_campain_list

23392624691

205

114110364

大維表

已經定位到RDS資料庫中個別欄位中由\n導致,還沒有做SCD2拉鍊表

dim_adn_advertiser_list

180283

197

915

943

小維表


dim_adn_campaign_list每天產生上億量級記錄,如圖2所示:


                                                         圖2

        在構建數倉的整個過程中有可能會出現的問題以及對應的解決方案(原則上要保證在查詢資料時能夠快速查詢):

1.有的維度欄位資料變化比較慢,儘管是採用SCD2技術,資料量依然較大,通過技術評估,分割槽技術不是太好解決,採用分桶技術;

2.有的維度表字段變化比較快,這種可以考慮採用分割槽技術,例如一個月或者一個季度一個分割槽,把誇分割槽的生效時間和失效時間在分割槽時間點進行切分;

3.資料量比較大的維度表不止一個,如果是一個分桶就建立在大維度表與事實表關聯的欄位上面,這樣可以採用hive的優化機制,bucket_join來大大減少表關聯的資料量(set hive.enforce.bucketing=true設定該引數(預設為false),分桶才生效);對這種多個維度表都比較大的,採用一種變通的方式(採用反向箭頭),讓維度表的關聯欄位指向事實表的主鍵,而不是採用在事實表中打維度表的代理鍵;在事實表的主鍵上面建立分桶的關聯欄位,然後其他三個大維度表通過關聯得到事實表上的主鍵來進行查詢(對應的SQL如下),在維度表上建立分桶,且分桶欄位是指向事實表的主鍵欄位,且維度表的分桶數量是事實表的倍數(因為事實表已經做過聚合操作,資料量大概在兩千多萬的量級)。

4.在廣告維度進行開發過程中發現異常資料,把表的儲存格式轉換成text格式後,如何快速定位到這條異常資料所做的資料檔案,通過在hiveSQL中新增虛列:1).  INPUT__FILE__NAME顯示map任務讀入File的全路徑;2).  BLOCK__OFFSET__INSIDE__FILE,如果是RCFile或者是SequenceFile塊壓縮格式檔案則顯示Block file Offset,如果是TextFile,顯示當前行的第一個位元組在檔案中的偏移量;3).  ROW__OFFSET__INSIDE__BLOCK        RCFile和SequenceFile顯示row number, textfile顯示為0,要顯示ROW__OFFSET__INSIDE__BLOCK ,必須設定set hive.exec.rowoffset=true;

SQL樣例:

fact_adn_tracking_click表有 兩千萬 的記錄,把fact_adn_tracking_click 分發到所有的 map 上也是個不小的開銷,而且 map join 不支援這麼大的小表。如果用普通的 join,又會碰到資料傾斜的問題。 

解決思路通過mapjoin僅僅查詢出需要的欄位進行:

 insert overwrite table dim_adn_creative_list 
  select  /*+mapjoin(c)*/c.id,d.*
      from ( select distinct id,creative_id from fact_adn_tracking_click) c
      join dim_adn_creative_list_snapshot  d

      on c.creative_id = d.creative_list_id

目標表建表語句:

CREATE EXTERNAL TABLE `dim_adn_creative_list`(
  `fact_sk` int COMMENT 'surrogate key', 
  `creative_list_id` bigint, 
  `creative_name` string, 
  `user_id` bigint, 
  `campaign_id` bigint, 
  `type` int, 
  `lang` int, 
  `height` int, 
  `width` int, 
  `image` string, 
  `text` string, 
  `comment` string, 
  `stime` bigint, 
  `etime` bigint, 
  `status` int, 
  `timestamp` bigint, 
  `tag` int, 
  `version` int, 
  `effective_date` string, 
  `expiry_date` string)
CLUSTERED BY ( 
  creative_list_sk) 
INTO 10000 BUCKETS
ROW FORMAT SERDE 
  'org.apache.hadoop.hive.ql.io.orc.OrcSerde' 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.orc.OrcInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.orc.OrcOutputFormat'
LOCATION
  's3://mob-emr-test/dataplatform/DataWareHouse/mobvista_dwh/tables/perm/dim_adn_creative_list';

---------------------------普通表,分桶普通表,分桶ORC格式------------------------------------------------------

CREATE  EXTERNAL TABLE `dim_adn_creative_list_perfomance`(
  `creative_list_sk` int, 
  `creative_list_id` bigint, 
  `creative_name` string, 
  `user_id` bigint, 
  `campaign_id` bigint, 
  `type` int, 
  `lang` int, 
  `height` int, 
  `width` int, 
  `image` string, 
  `text` string, 
  `comment` string, 
  `stime` bigint, 
  `etime` bigint, 
  `status` int, 
  `timestamp` bigint, 
  `tag` int, 
  `version` int, 
  `effective_date` string, 
  `expiry_date` string)
ROW FORMAT SERDE 
  'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe' 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.mapred.TextInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
  's3://mob-emr-test/dataplatform/DataWareHouse/mobvista_dwh/tables/perm/dim_adn_creative_list_perfomance';

CREATE EXTERNAL TABLE `dim_adn_creative_list_perfomance_bucket`(
  `creative_list_sk` int, 
  `creative_list_id` bigint, 
  `creative_name` string, 
  `user_id` bigint, 
  `campaign_id` bigint, 
  `type` int, 
  `lang` int, 
  `height` int, 
  `width` int, 
  `image` string, 
  `text` string, 
  `comment` string, 
  `stime` bigint, 
  `etime` bigint, 
  `status` int, 
  `timestamp` bigint, 
  `tag` int, 
  `version` int, 
  `effective_date` string, 
  `expiry_date` string)
CLUSTERED BY ( 
  creative_list_id) 
SORTED BY ( 
  creative_list_id ASC) 
INTO 50 BUCKETS
ROW FORMAT DELIMITED 
  FIELDS TERMINATED BY '\t' 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.mapred.TextInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
's3://mob-emr-test/dataplatform/DataWareHouse/mobvista_dwh/tables/perm/dim_adn_creative_list_perfomance_bucket';

CREATE EXTERNAL TABLE `dim_adn_creative_list_orc_bucket`(
  `creative_list_sk` int COMMENT 'surrogate key', 
  `creative_list_id` bigint, 
  `creative_name` string, 
  `user_id` bigint, 
  `campaign_id` bigint, 
  `type` int, 
  `lang` int, 
  `height` int, 
  `width` int, 
  `image` string, 
  `text` string, 
  `comment` string, 
  `stime` bigint, 
  `etime` bigint, 
  `status` int, 
  `timestamp` bigint, 
  `tag` int, 
  `version` int, 
  `effective_date` string, 
  `expiry_date` string)
CLUSTERED BY ( 
  creative_list_sk) 
INTO 50 BUCKETS
ROW FORMAT SERDE 
  'org.apache.hadoop.hive.ql.io.orc.OrcSerde' 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.orc.OrcInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.orc.OrcOutputFormat'
LOCATION
  's3://mob-emr-test/dataplatform/DataWareHouse/mobvista_dwh/tables/perm/dim_adn_creative_list_orc_bucket';