數倉數據分層簡介
1. 背景
- 數倉是什麼, 其實就是存儲數據,體現歷史變化的一個數據倉庫. 因爲互聯網時代到來,基於數據量的大小,分爲了傳統數倉和現代數倉.
- 傳統數倉,使用傳統的關係型數據庫進行數據存儲,因爲關係型數據庫本身可以使用SQL以及函數等做數據分析.所以把數據存儲和數據分析功能集合爲一體,加上一個可視化界面,就能從數據存儲,數據分析,數據展示完整方案.
- 到了互聯網時代,由於上網用戶劇增,特別是移動互聯網時代,海量的網絡設備,導致了海量的數據產生,企業需要也希望從這些海量數據中挖掘有效信息,如行爲日誌數據,業務數據,爬蟲數據等等中提煉出有價值信息.但傳統的關係型數據庫由於本身技術限制,無法很好承擔這麼大數據量存儲和分析的任務,現代大數據技術應運而生.而數倉就是做這些海量數據存儲的地方.
2. 經典數倉分層架構
- 其實數倉數據分層,就跟代碼分層一樣.如果所有數據都放在一層,就跟代碼都放在一個文件,肯定是可以運行的,但帶來的問題就是閱讀性差,複用性和維護性降低.
數倉的分層也是一樣,每一層都有自己的職責,同時都是基於下一層或者下面多層做數據處理之後的結果.
這樣一來,最上層就是ADS,數據應用層,當更上層需要數據時,不需要再從最底層進行數據計算,可以複用中間層級的現有結果,可以提升數據處理速度.
同樣的,因爲更上層數據都是從下一層或者下面多層數據處理而來,這樣就算下層數據丟失,也不會造成企業所有數據毀滅性災難,算是一種數據冗餘機制,不過更上層數據一般做了數據處理,提升了維度信息.
-
需要注意,大數據的特點之一就是海量數據,但是數據價值密度較低.
使用數據分層機制,相當於提煉黃金的流程,逐步逐步將有價值信息進行彙總聚合,這樣就跟分步操作一樣,最終提煉出想要的結果.同時就算原始數據丟失了,只要中間結果還在,依然可以保證最上層數據的穩定性.類似加了一層緩衝一樣 -
大數據的特點,海量數據,這其實帶來了較大的存儲壓力.將數據進行分層之後, 最原始的數據存儲週期就可以適當降低,這樣可以降低存儲壓力.而更上層的數據,因爲都是加工後的數據,數據量相對較少,存儲壓力就會小一些,存儲週期也就可以長一些.
綜上,道理其實都是相通的,數據分層最終遵循的還是很樸素的道理.降低存儲壓力,降低企業使用成本,複用中間數據結果,提升數據處理速度.逐步處理,這樣可以更加靈活應對企業開發需求,不需要所有需求都從數據量最大的ODS層進行數據處理
3. 模型設計
3.1 ODS層
- 這一層又叫做貼源層,就是接近數據源的一層,需要存儲的數據量是最大的,存儲的數據也是最原始,最真實未經過太多處理的數據.
- 按照目前大數據企業開發的數據來源來看,不管是爬蟲數據,日誌數據還是業務數據,都會有一層ODS層,存放最原始的數據.
- 注意,ODS層數據還起到一個數據備份作用,如果是比較特殊行業,在ODS層的數據會保留一年甚至多年.不過普通公司一般就保存3–6個月,看數據量和存儲壓力以及存儲預算決定.
- 日誌數據估算,如日活100萬用戶,每個用戶訪問1次,每次操作5min,每個用戶平均3秒一條日誌數據,一條數據1kb.最後體積是100w*1*5*60/3*1kb=100w*100kb=97656.25MB=95.36GB;
注意,數據估算最好結合公司實際情況,如果已經運行一段,可以讓運維同事幫忙做估算
因爲數據本身可以做壓縮,數倉數據還需要做分層,數據本身存儲時還會有備份機制(HDFS\Kafka等框架)
數據還會源源不斷增長,同時磁盤還需要預留一定緩衝空間,一般是30%緩衝空間.所以除非是新建項目或者遇到超快速增長的公司,一般的大數據容量評估都是按照最高上限做半年甚至一年總容量做評估的.
注意,在服務器領域,磁盤的成本相對CPU\內存來說,成本是相對最低的,甚至有專門的存儲服務器,如24硬盤位,甚至48硬盤位的服務器.
而2020年先在已經開發出了單磁盤12TB甚至14TB的企業硬盤,這意味着單節點機器容量上限進一步提升,存儲成本也隨着技術提升,成本開始降低下來.
- ODS中數據不意味着裏面的數據就是最原始沒經過處理的.一般企業開發,因爲真實環境中數據上報,存儲,採集中錯誤,bug,網絡等問題,會造成原始數據的各類問題.
- 字段缺失
- 數據字段不統一
- 格式錯誤
- 關鍵信息丟失等等
- 數據來源混雜
- 數據類型不一,例如json,xml,text,csv的,壓縮了的,沒有壓縮的等等.
- 一般企業開發時,都會對原始數據存入到ODS時,做一些最基本的處理
- 數據來源區分
- 數據按照時間分區存儲,一般是按照天,也有公司使用年,月,日三級分區做存儲的
- 進行最基本的數據處理,如格式錯誤的丟棄,關鍵信息丟失的過濾掉等等.
注意,有的公司ODS層不會做太多數據過濾處理,會放到DWD層來處理.
有的公司會在一開始時就在ODS層做數據相對精細化的過濾.這個並沒有明確規定,看每個公司自己的想法和技術規範
- ODS層建立表時,如果使用hive進行處理,一般建立外部表.
hive的外部表,對應的是業務表;
hive外部表,存放數據的文件可以不是在hive的hdfs默認的位置,並且hive對應的表刪除時,相應的數據文件並不會被刪除.這樣對於企業開發來說,可以防止因爲刪除表的操作而把寶貴的數據刪除掉
hive的業務表,則相反.數據文件存放在hive對應的默認位置,表刪除時,對應文件也會被刪除掉.
大數據開發,使用hive時,一般都是使用外部表
create external table xxx(
)
- 1
- 2
- 3
- ODS層的文件格式
- 如果數據來自於日誌文件,一般和原始日誌文件格式一樣.
- 如果數據來自於數據庫,則看需要.
- 數據分區,一般都是按照天進行分區,如使用hive,則partitioned by 一般都是按照天進行存放.
實際企業開發,並沒有定法,有些公司的ODS層數據,採取壓縮方式存放,如parquet這類列式存儲,帶索引,帶壓縮的文件格式.這樣既可以降低存儲壓力,也能提升查詢效率,還有很好的框架兼容性
- 特別點,
- 如果使用hive做ODS數據存儲管理,遇到JSON時,可以使用JsonSerde進行數據解析.
- JsonSerde 的 github 地址:https://github.com/rcongiu/Hive-JSON-Serde
JsonSerde 的 jar下載地址:http://www.congiu.net/hive-json-serde/
下載 json-serde-1.3.7-jar-with-dependencies.jar 並上傳到 Hive的/lib庫目錄下- 也可以把本jar包安裝到本地maven庫
bin\mvn install:install-file -Dfile=d:/json-serde.1.3.8.jar -DgroupId=“org.openx.data” -DartifactId=json-serde -Dversion=“1.3.8” -Dpackaging=jar
drop table app_event_log;
create external table ods.app_event_log
(
account string,
appId string,
appVersion string,
carrier string,
deviceId string,
deviceType string,
eventId string,
ip string,
latitude double,
longitude double,
netType string,
osName string,
osVersion string,
properties map<string,string>,
releaseChannel string,
resolution string,
sessionId string,
`timeStamp` bigint
)
partitioned by (y string,m string,d string)
row format serde 'org.openx.data.jsonserde.JsonSerDe'
stored as textfile
;
- 如果需要將數據導入HBase, 可以使用BulkLoad方式,批量導入.
不過實際企業開發中,越來越少企業使用HBase進行ODS數據存儲.
一個是rowkey限制維度信息,
第二個是SQL支持不好,雖然有Phoenix,但是對比Hive還是有不足
而且使用Hive管理數據,後續使用Kylin,presto還有很好的兼容性
3.2 DWD層
- 按照建模思想,不完全星型建模. 注意,所有的框架實際實現時,都不會按照理想方式去實現,都會在實現成本,複雜度,性能,指標等方面綜合考慮.
- DWD又叫做數據明細表, 很多時候存儲的都是事實表爲主.
- 在DWD層,會有ETL,也就是extract transform load 提取轉換加載處理,邏輯會比較複雜,這時候如果使用hive,一般無法滿足要求,這些邏輯一般都是編寫代碼實現,然後使用腳本進行週期性如每天調用.
- 去除廢棄字段,去除格式錯誤的信息
- 去除丟失了關鍵字段的信息
- 去除不含時間信息的數據(這個看公司具體業務,但一般數據中都會帶上時間戳,這樣方便後續處理時,進行時間維度上信息分析處理和提取)
- 有些公司還會在這一層將數據打平,不過這具體要看業務需求.這是因爲kylin適合處理展平後數據,不適合處理嵌套的表數據信息.
- 有些公司還會將數據session做切割,這個一般是app的日誌數據,其他業務場景不一定適合.這是因爲app有進入後臺模式,例如用戶上午打開app用了10分鐘,然後app切入後臺,晚上再打開,這時候session還是一個,實際上應該做切割纔對.(也有公司會記錄app進入後臺,再度進入前臺的記錄,這樣來做session切割)
- 數據規範化,因爲大數據處理的數據可能來資源公司不同部門,不同項目,不同客戶端,這時候可能相同業務數據字段,數據類型,空值等都不一樣,這時候需要在DWD層做抹平.否則後續處理使用時,會造成很大的困擾.
簡單的,如boolean,有使用0 1標識,也有使用true false標識的
如字符串空值,有使用"",也有使用null,的,統一爲null即可
如日期格式,這種就差異性更大,需要根據實際業務數據決定,不過一般都是格式化爲YYYY-MM-dd HH:mm:ss 這類標準格式
- 注意,事實表中數據,一般不是所有維度都按照維度主鍵做信息存儲.
維度退化,其實從代碼角度來說,就是當一個代碼寫死之後,失去了靈活性,維度就退化了.
在數倉理論中,有幾個經典思想,一個是去除數據冗餘.所以一般會把維度信息單獨存放,其他表要使用時,記錄對應維度的id即可.
這樣,就算維度表中數據發生了變化,其他表數據因爲只是記錄了id,不會有影響.
同時,維度信息放在一張表中存放,而不是每個表中存儲一份,將來需要調整,只需要做一次工作即可,降低了數據冗餘.
這一點和代碼的實現和設計思想是一致的,不要重複造輪子.
- 在DWD層,一般還會做數據映射,
- 例如將GPS經緯度轉換爲省市區詳細地址.
業界常見GPS快速查詢一般將地理位置知識庫使用geohash映射,然後將需要比對的GPS轉換爲geohash後跟知識庫中geohash比對,查找出地理位置信息
對於geohash感興趣,可以看我另外一篇博文.
當然,也有公司使用open api,如高德地圖,百度地圖的api進行GPS和地理位置信息映射,但這個達到一定次數需要花錢,所以大家都懂的. - 會將IP地址也轉換爲省市區詳細地址.
這個有很多快速查找庫,不過基本原理都是二分查找,因爲ip地址可以轉換爲長整數.典型的如ip2region庫 - 將時間轉換爲年,月,日甚至周,季度維度信息.
這樣帶來好處就是,後續業務處理時如果需要這些信息,直接使用即可.不過會一定程度增加數據量,但一般都還可以接收,增加並不多.
注意,數據映射一般只映射常見指標以及明確的企業開發中後續會用到的指標,因爲數據量較大,如映射的指標後續用不到,只會平白增加開發,維護成本.
- DWD存儲數據,一般就是維度表,事實表,實體表等數據.
- 維度表,顧名思義,就是一些維度信息,這種表數據,一般就直接存儲維度信息,很多時候維度表都不會很大.
- 事實表,就是表述一些事實信息,如訂單,收藏,添加購物車等信息.這種數據量較大,同時因爲數據可能快速或者緩慢變化,這種一般存儲維度主鍵,具體維度值在後續處理分析時再臨時關聯
- 實體表,類似javabean,用來描述信息的,如優惠券表,促銷表.內部就是一些描述信息.這種一般看數據量以及變化程度,大部分時候都是全量導入,導入週期則看具體而定.
- id mapping
考慮如下情況:
- 對於互聯網企業來說,目前用戶來源基本就是web app 小程序三種來源.
- 當用戶登錄之後訪問,毫無疑問,使用用戶賬號id標識用戶即可,這是唯一的.但也需要分情況考慮
- 如果之前登錄訪問的是一個賬號,現在換了一個賬號,設備該綁定哪個賬號?
- 當用戶未登錄訪問,這時候就需要劃分情況了
- 如果用戶之前用這個設備已經登錄過,這次沒登陸要如何處理.
- 如果這個設備之前有一個賬號登錄使用過,現在未登錄使用
- 如果這個設備沒有賬號登錄使用過,現在未登錄使用
這是爲每一個用戶生成一個全局唯一標識的過程.主要是將匿名訪問用戶綁定到一個全局唯一id上
合適的用戶標識對於提高用戶行爲分析準確性有很大的影響,這是DWD層最關鍵的一個技術設計點
這對於漏斗分析,留存,session等關鍵指標準確性至關重要
在採集到的數據中,可以使用app端的deviceid,userid,可以使用web端的cookieid,ip,userid,可以使用小程序數據中的openid,userid
而實際生活中,用戶可能有很複雜的使用狀態,而產生的數據需要儘可能覆蓋多種情況,這樣可以讓結果儘量貼近真實情況.
登錄狀態訪問app
匿名狀態訪問app
登錄狀態訪問web
匿名狀態訪問web
登錄狀態訪問wx小程序
匿名狀態訪問wx小程序
一個用戶可能擁有不止一臺終端設備
一臺終端設備上可能有多個用戶使用
一個用戶可能一段時間後更換手機
方案1:
只使用設備 ID
適合沒有用戶註冊體系,或者極少數用戶會進行多設備登錄的產品,如工具類產品、搜索引擎、部分小型電商等。
這也是絕大多數數據分析產品唯一提供的方案。
不足點:
同一用戶在不同設備使用會被認爲不同的用戶,對後續的分析統計有影響。
不同用戶在相同設備使用會被認爲是一個用戶,也對後續的分析統計有影響。
但如果用戶跨設備使用或者多用戶共用設備不是產品的常見場景的話,可以忽略上述問題
方案2:
關聯設備 ID 和登錄 ID(一對一)
適合場景,成功關聯設備 ID 和登錄 ID 之後,用戶在該設備 ID 上或該登錄 ID 下的行爲就會貫通,被認爲是一個 全局 ID 發生的。在進行事件、漏斗、留存等用戶相關分析時也會算作一個用戶。
關聯設備 ID 和登錄 ID 的方法雖然實現了更準確的用戶追蹤,但是也會增加複雜度。
所以一般來說,我們建議只有當同時滿足以下條件時,才考慮進行 ID 關聯:
需要貫通一個用戶在一個設備上註冊前後的行爲。
需要貫通一個註冊用戶在不同設備上登錄之後的行爲
不足點:
一個設備 ID 只能和一個登錄 ID 關聯,而事實上一臺設備可能有多個用戶使用。
一個登錄 ID 只能和一個設備 ID 關聯,而事實上一個用戶可能用一個登錄 ID 在多臺設備上登錄
方案3:
一個用戶在多個設備上進行登錄是一種比較常見的場景,比如 Web 端和 App 端可能都需要進行登錄。支持一個登錄 ID 下關聯多設備 ID 之後,用戶在多設備下的行爲就會貫通,被認爲是一個ID 發生的。
不足點:
一個設備 ID 只能和一個登錄 ID 關聯,而事實上一臺設備可能有多個用戶使用。
一個設備 ID 一旦跟某個登錄 ID 關聯或者一個登錄 ID 和一個設備 ID 關聯,就不能解除(自動解除)。
而事實上,設備 ID 和登錄 ID 的動態關聯才應該是更合理的
方案4:
關聯設備 ID 和登錄 ID(動態修正)
基本原則,與方案3相同
修正之處,一個設備ID被綁定到某個登陸ID(A)之後,如果該設備在後續一段時間(比如一個月內)被一個新的登陸ID(B)更頻繁使用,則該設備ID會被調整至綁定登陸ID(B)
核心點在於同一臺設備不同登錄賬號,根據一定規則打分,設備關聯哪個賬號根據得分最高者來決定.
一般是登陸次數多的得分高,但如果此前登錄次數多,但類似賣出手機場景,新用戶新賬號登錄次數變多,則舊帳號會持續扣分,新賬號會持續加分,最後新賬號得分高出.
扣分規則一般是乘以一個負數,這樣降低梯度對比減法會更快.
實現案例:
- 加載T日日誌數據,抽取設備id 登錄賬號id, 會話id, 時間戳
- 根據設備id + 登陸賬號account分組,計算每個設備上每個登錄賬號的登錄評分.登陸了就打分,沒登陸就扣分
- 加載T-1日綁定評分結果表
- 將T日評分表 full join T-1日評分表,如果兩邊都有數據,則說明連續登陸了,加分.如果T有,T-1沒有,則扣分
object IdBind {
def main(args: Array[String]): Unit = {
Logger.getLogger("org").setLevel(Level.WARN)
val spark = SparkSession.builder()
.config("spark.sql.shuffle.partitions","2")
.enableHiveSupport() // 開啓hive整合支持(同時,需要引入spark-hive的依賴;引入hadoop和hive的配置文件)
.appName("演示")
.master("local")
.getOrCreate()
// 加載T日日誌數據
val logDf = spark.read.table("ods.app_action_log").where("dt='2020-10-07'")
logDf.createTempView("logdf")
// 計算T日的 設備->賬號 綁定得分
val loginCnts = spark.sql(
"""
|
|select
|deviceid,
|if(account is null or trim(account)='',null,account) as account,
|-- count(distinct sessionid) as login_cnt,
|min(timestamp) as first_login_ts,
|count(distinct sessionid)*100 as bind_score
|from logdf
|group by deviceid,account
|
|""".stripMargin)
loginCnts.createTempView("today")
println("當天評分結果")
loginCnts.show(100)
// 加載 T-1的 綁定得分 (從hive的綁定評分表中加載)
// val bindScorePre = spark.read.parquet("dataware/data/idbind/output/day01")
val bindScorePre = spark.read.table("dwd.id_account_bind").where("dt='2020-10-06'")
println("歷史評分結果")
bindScorePre.show(100)
bindScorePre.createTempView("yestoday")
// 全外關聯兩個綁定得分表
// 並將結果寫入hive表的當天分區(T-1日分區就無用了)
val combined = spark.sql(
"""
|
|insert into table dwd.id_account_bind partition(dt='2020-10-07')
|
|select
|if(today.deviceid is null,yestoday.deviceid,today.deviceid) as deviceid,
|if(today.account is null,yestoday.account,today.account) as account,
|if(yestoday.first_login_ts is not null,yestoday.first_login_ts,today.first_login_ts) as first_login_ts,
|-- if(today.account is null,yestoday.login_cnt,today.login_cnt+yestoday.login_cnt) as login_cnt,
|if(today.account is null,yestoday.bind_score*0.9,today.bind_score+if(yestoday.bind_score is null,0,yestoday.bind_score)) as bind_score
|from
| today
|full join
| yestoday
|on today.deviceid=yestoday.deviceid and today.account=yestoday.account
|
|""".stripMargin)
spark.close()
}
}
3.3 DWS層
- DWS,俗稱的數據服務層,也有叫做數據聚合層.不過按照經典數據建模理論,一般稱之爲前者,也就是數據服務層,爲更上層的ADS層或者直接面向需求方服務.
- DWS建模,一般使用主題建模,維度建模等方式
- 主題建模,顧名思義,圍繞某一個業務主體進行數據建模,將相關數據抽離提取出來.
- 如,將流量會話按照天,月進行聚合
- 將每日新用戶進行聚合
- 將每日活躍用戶進行聚合
- 維度建模,其實也差不多,不過是根據業務需要,提前將後續數據查詢處理需要的維度數據抽離處理出來,方便後續查詢使用.
- 如將運營位維度數據聚合
- 將渠道拉新維度數據聚合
3.4 ADS層
- 這是應用服務層,一般就直接對接OLAP分析,或者業務層數據調用接口了
- 這是最頂層,一般都是結果類型數據,可以直接拿去使用或者展示的數據了.也是對數據抽離分析程度最高的一層數據
- 這一層是需求最明確的一層,根據業務需求來決定數據維度和結果分析.類似代碼最外層,接口是相對最固化的.