寫在前面: 博主是一名軟件工程系大數據應用開發專業大二的學生,暱稱來源於《愛麗絲夢遊仙境》中的Alice和自己的暱稱。作爲一名互聯網小白,
寫博客一方面是爲了記錄自己的學習歷程,一方面是希望能夠幫助到很多和自己一樣處於起步階段的萌新
。由於水平有限,博客中難免會有一些錯誤,有紕漏之處懇請各位大佬不吝賜教!個人小站:http://alices.ibilibili.xyz/ , 博客主頁:https://alice.blog.csdn.net/
儘管當前水平可能不及各位大佬,但我還是希望自己能夠做得更好,因爲一天的生活就是一生的縮影
。我希望在最美的年華,做最好的自己
!
經過了用戶畫像,標籤系統的介紹,又經過了業務數據調研與ETL處理之後,本篇博客,我們終於可以迎來【企業級用戶畫像】之標籤開發。
文章目錄
我們根據標籤的計算方式的不同,我們將所有的標籤劃分成3種不同的類型:
■ 匹配型:通過匹配對應的值來確定標籤結果
■ 統計型:按照一定的範圍進行彙總分類得到標籤結果
■ 挖掘型:需要通過多個維度利用一定的算法才能得到的標籤
如果是匹配和統計型標籤,我們只需要從數據庫中將對應的業務數據查詢出來,分析即可。但如果涉及到了挖掘型標籤,就不可避免地涉及到機器學習的算法使用。
但標籤開發流程大體如下:
根據流程,我們的開發思路如下:
從MySQL中獲取4級和5級的數據:id和rule
從4級rule中獲取HBase數據源信息
從5級rule中獲取匹配規則
加載HBase數據源
根據需求進行標籤計算
數據落地
由於篇幅問題,本篇博客我們先來介紹匹配型標籤的開發代碼書寫。
導入pom依賴
<properties>
<scala.version>2.11.8</scala.version>
<spark.version>2.2.0</spark.version>
<hbase.version>1.2.0-cdh5.14.0</hbase.version>
<solr.version>4.10.3-cdh5.14.0</solr.version>
<mysql.version>8.0.17</mysql.version>
<slf4j.version>1.7.21</slf4j.version>
<maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>
<build-helper-plugin.version>3.0.0</build-helper-plugin.version>
<scala-compiler-plugin.version>3.2.0</scala-compiler-plugin.version>
<maven-shade-plugin.version>3.2.1</maven-shade-plugin.version>
</properties>
<dependencies>
<!-- Spark -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-mllib_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.scalanlp</groupId>
<artifactId>breeze_2.11</artifactId>
<version>0.13</version>
</dependency>
<!-- HBase -->
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>${hbase.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-common</artifactId>
<version>${hbase.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>${hbase.version}</version>
</dependency>
<!-- Solr -->
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-core</artifactId>
<version>${solr.version}</version>
</dependency>
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>${solr.version}</version>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>cn.itcast.up29</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>${build-helper-plugin.version}</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>src/main/java</source>
<source>src/main/scala</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
<verbose>true</verbose>
<fork>true</fork>
</configuration>
</plugin>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>${scala-compiler-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
<configuration>
<args>
<arg>-dependencyfile</arg>
<arg>${project.build.directory}/.scala_dependencies</arg>
</args>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<!--這裏要替換成jar包main方法所在類 -->
<mainClass>cn.itcast.up29.TestTag</mainClass>
</manifest>
<manifestEntries>
<Class-Path>.</Class-Path>
</manifestEntries>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- 指定在打包節點執行jar包合併操作 -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>cloudera</id>
<url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
</repository>
</repositories>
HBase元數據樣例類
爲了方便在後面主程序中對元數據信息進行封裝調用,我們這裏先提前定義好所需要使用到的樣例類。
case class HBaseMeta (
inType: String,
zkHosts: String,
zkPort: String,
hbaseTable: String,
family: String,
selectFields: String,
rowKey: String
)
object HBaseMeta{
val INTYPE = "inType"
val ZKHOSTS = "zkHosts"
val ZKPORT = "zkPort"
val HBASETABLE = "hbaseTable"
val FAMILY = "family"
val SELECTFIELDS = "selectFields"
val ROWKEY = "rowKey"
}
case class TagRule(
id:Int,
rule:String
)
HBase數據源source
因爲我們在進行標籤的開發過程中,需要讀取Hbase中的數據,若使用常規的方法,從hbase 客戶端讀取效率較慢,所以我們本次將hbase作爲【數據源】,這樣讀取效率較快。
將hbase作爲【數據源】來進行操作,我們需要提前定義工具類。
圖示工具類代碼較多,這裏就不貼出來了,感興趣的朋友可以後臺找我獲取。
性別標籤主程序
在準備好了樣例類和工具類代碼後,我們正式開始寫主程序的代碼。因爲本篇博客是對匹配型標籤進行開發,這裏我們以人口屬性標籤分類下的性別標籤爲例進行開發。
定義主程序入口,並連接jdbc
根據流程圖,我們需要先讀取MySQL中的數據,所以我們先連接JDBC。這裏爲了後續對MySQL元數據信息的一個封裝,還定義了一個方法進行數據的封裝。
object GenderTag {
// 程序的入口
def main(args: Array[String]): Unit = {
// 1. 創建SparkSQL
// 用於讀取mysql , hbase等數據
val spark: SparkSession = SparkSession.builder().appName("GenderTag").master("local[*]").getOrCreate()
//2 連接mysql 數據庫
//url: String, table: String, properties: Properties
// 設置Spark連接MySQL所需要的字段
var url: String ="jdbc:mysql://bd001:3306/tags_new2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&user=root&password=123456"
var table: String ="tbl_basic_tag" //mysql數據表的表名
var properties:Properties = new Properties
//連接mysql
val mysqlConn: DataFrame = spark.read.jdbc(url,table,properties)
}
//將mysql中的四級標籤的rule 封裝成HBaseMeta
//方便後續使用的時候方便調用
def toHBaseMeta(KVMap: Map[String, String]): HBaseMeta = {
//開始封裝
HBaseMeta(KVMap.getOrElse("inType",""),
KVMap.getOrElse("zkHosts",""),
KVMap.getOrElse("zkPort",""),
KVMap.getOrElse("hbaseTable",""),
KVMap.getOrElse("family",""),
KVMap.getOrElse("selectFields",""),
KVMap.getOrElse("rowKey","")
)
}
}
讀取MySQL四級標籤
通過讀取MySQL中的四級標籤,我們可以爲讀取hbase數據做準備(因爲四級標籤的屬性中含有hbase的一系列元數據信息)。又因爲通過查詢數據庫獲取到的結果類型不利於我們樣例類的封裝,所以我們還需要導入隱式轉換,方便將其進行格式的轉變。
//引入隱式轉換
import spark.implicits._
//引入java 和scala相互轉換
import scala.collection.JavaConverters._
//引入sparkSQL的內置函數
import org.apache.spark.sql.functions._
//3 讀取Mysql數據庫的四級標籤
// 爲讀取hbase數據做準備
val fourTagsDS: Dataset[Row] = mysqlConn.select("id","rule").where("id=4")
// 這個字符串讀取數據不好用 inType=HBase##zkHosts=192.168.10.20##zkPort=2181##hbaseTable=tbl_users##family=detail##selectFields=id,gender
// 將上述數據轉爲樣例類,以便於後面讀取數據
// 遍歷四級標籤數據fourTags
val KVMap: Map[String, String] = fourTagsDS.map(row => {
// 獲取到rule的值
val RuleValue: String = row.getAs("rule").toString
/*
inType=HBase##
zkHosts=192.168.10.20##
zkPort=2181##
hbaseTable=tbl_users##
family=detail##
selectFields=id,gender
*/
//使用“##”對數據繼續切分
val KVMaps: Array[(String, String)] = RuleValue.split("##").map(kv => {
val arr: Array[String] = kv.split("=")
//zkHosts 192.168.10.20 , zkPort 2181
(arr(0), arr(1))
})
KVMaps
}).collectAsList().get(0).toMap
// 考慮到KVMaps的返回值類型爲Dataset[Array[(String, String)]]
// 爲了後續方便調用,我們這裏引入隱式轉換,使用collectAsList將其轉換成List,再轉換成的Map
println(KVMap)
/*
Map(selectFields -> id,gender, inType -> HBase, zkHosts -> 192.168.10.20, zkPort -> 2181, hbaseTable -> tbl_users, family -> detail)
*/
//開發toHBaseMeta方法 將KVMap 封裝成爲樣例類HBaseMeta
var hbaseMeta:HBaseMeta=toHBaseMeta(KVMap)
讀取MySQL五級標籤
獲取完了四級標籤,我們這裏再來獲取五級標籤。同樣,爲了方便後續使用,也使用到了隱式轉換所提供的方法,將查詢的結果轉換成了List類型。
//4 讀取mysql數據庫中的五級標籤
// 匹配性別
val fiveTagsDS: Dataset[Row] = mysqlConn.select('id ,'rule).where("pid=4")
// 獲取出id 和 rule
// 將FiveTagsDS 封裝成樣例類TagRule
val fiveTageList: List[TagRule] = fiveTagsDS.map(row => {
// row 是一條數據
// 獲取出id 和 rule
val id: Int = row.getAs("id").toString.toInt
val rule: String = row.getAs("rule").toString
// 封裝樣例類
TagRule(id,rule)
}).collectAsList() // 將DataSet轉換成util.List[TagRule] 這個類型遍歷時無法獲取id,rule數據
.asScala.toList // 將util.List轉換成list 需要隱式轉換 import scala.collection.JavaConverters._
// for(a<- fiveTageList){
// println(a.id+" "+a.rule)
// }
//5 1
//6 2
讀取Hbase中的數據
通過前面對於四級標籤的一個查詢,我們將Hbase元數據信息以及封裝成了一個樣例類。這裏在進行連接的時候,直接通過對象.的形式進行調用,確實簡單方便了許多。
// 5. 根據mysql數據中的四級標籤, 讀取hbase數據
// 若使用hbase 客戶端讀取效率較慢,將hbase作爲【數據源】,讀取效率較快
val hbaseDatas: DataFrame = spark.read.format("com.czxy.tools.HBaseDataSource")
// hbaseMeta.zkHosts 就是 192.168.10.20 和 下面是兩種不同的寫法
.option("zkHosts",hbaseMeta.zkHosts)
.option(HBaseMeta.ZKPORT, hbaseMeta.zkPort)
.option(HBaseMeta.HBASETABLE, hbaseMeta.hbaseTable)
.option(HBaseMeta.FAMILY, hbaseMeta.family)
.option(HBaseMeta.SELECTFIELDS, hbaseMeta.selectFields)
.load()
// 展示一些數據
hbaseDatas.show(5)
/*
+---+------+
| 1| 2|
| 10| 2|
|100| 2|
|101| 1|
|102| 2|
標籤匹配
已經獲取到了MySQL中五級標籤和Hbase數據庫中的內容,我們就可以進行標籤的一個匹配。
// 6 標籤匹配
// 根據五級標籤數據和hbase數據進行標籤匹配 得到最終的標籤
// 編寫udf函數 例如輸入是1,2 返回不同性別對應的id值5或者6
val GetTagId: UserDefinedFunction = udf((gender: String) => {
// 設置標籤默認值
var id: Int = 0
// 遍歷五級標籤
for (ruleOb <- fiveTageList) {
// 當用戶數據的gender與五級標籤的id相等
// 那麼返回五級標籤的id
if (gender == ruleOb.rule) {
id = ruleOb.id
}
}
id
})
// 標籤匹配
val userTags: DataFrame = hbaseDatas.select('id.as("userId"),GetTagId('gender).as("tagsId"))
// 輸出查看效果
userTags.show()
/*
+------+------+
|userId|tagsId|
+------+------+
| 1| 6|
| 10| 6|
| 100| 6|
*/
將最終結果寫入到Hbase
已經得到結果,我們將其存儲進Hbase進行保存。
//7 將最終的標籤寫入Hbase
userTags.write.format("com.czxy.tools.HBaseDataSource")
.option("zkHosts", hbaseMeta.zkHosts)
.option(HBaseMeta.ZKPORT, hbaseMeta.zkPort)
.option(HBaseMeta.HBASETABLE,"test")
.option(HBaseMeta.FAMILY, "detail")
.option(HBaseMeta.SELECTFIELDS, "userId,tagsId")
.save()
完整源碼
爲了方便大家閱讀,這裏貼出完整源碼。
import java.util.Properties
import com.czxy.bean.{HBaseMeta, TagRule}
import org.apache.spark.sql._
import org.apache.spark.sql.expressions.UserDefinedFunction
/*
* @Auther: Alice菌
* @Date: 2020/6/4 15:26
* @Description:
流年笑擲 未來可期。以夢爲馬,不負韶華!
*/
/* Gender 用於性別標籤的計算 */
object GenderTag {
// 程序的入口
def main(args: Array[String]): Unit = {
// 1. 創建SparkSQL
// 用於讀取mysql , hbase等數據
val spark: SparkSession = SparkSession.builder().appName("GenderTag").master("local[*]").getOrCreate()
//2 連接mysql 數據庫
/* spark.read.format("jdbc")
.option("","")
.option("","")
.load() */
//url: String, table: String, properties: Properties
// 設置Spark連接MySQL所需要的字段
var url: String ="jdbc:mysql://bd001:3306/tags_new2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&user=root&password=123456"
var table: String ="tbl_basic_tag" //mysql數據表的表名
var properties:Properties = new Properties
//連接mysql
val mysqlConn: DataFrame = spark.read.jdbc(url,table,properties)
//引入隱式轉換
import spark.implicits._
//引入java 和scala相互轉換
import scala.collection.JavaConverters._
//引入sparkSQL的內置函數
import org.apache.spark.sql.functions._
//3 讀取Mysql數據庫的四級標籤
// 爲讀取hbase數據做準備
val fourTagsDS: Dataset[Row] = mysqlConn.select("id","rule").where("id=4")
// 這個字符串讀取數據不好用 inType=HBase##zkHosts=192.168.10.20##zkPort=2181##hbaseTable=tbl_users##family=detail##selectFields=id,gender
// 將上述數據轉爲樣例類,以便於後面讀取數據
// 遍歷四級標籤數據fourTags
val KVMap: Map[String, String] = fourTagsDS.map(row => {
// 獲取到rule的值
val RuleValue: String = row.getAs("rule").toString
/*
inType=HBase##
zkHosts=192.168.10.20##
zkPort=2181##
hbaseTable=tbl_users##
family=detail##
selectFields=id,gender
*/
//使用“##”對數據繼續切分
val KVMaps: Array[(String, String)] = RuleValue.split("##").map(kv => {
val arr: Array[String] = kv.split("=")
//zkHosts 192.168.10.20 , zkPort 2181
(arr(0), arr(1))
})
KVMaps
}).collectAsList().get(0).toMap
// 考慮到KVMaps的返回值類型爲Dataset[Array[(String, String)]]
// 爲了後續方便調用,我們這裏引入隱式轉換,使用collectAsList將其轉換成List,再轉換成的Map
println(KVMap)
/*
Map(selectFields -> id,gender, inType -> HBase, zkHosts -> 192.168.10.20, zkPort -> 2181, hbaseTable -> tbl_users, family -> detail)
*/
//開發toHBaseMeta方法 將KVMap 封裝成爲樣例類HBaseMeta
var hbaseMeta:HBaseMeta=toHBaseMeta(KVMap)
//println( hbaseMeta.hbaseTable+" "+ hbaseMeta.family+" "+ hbaseMeta.selectFields)
/*
tbl_users detail id,gender
*/
//4 讀取mysql數據庫中的五級標籤
// 匹配性別
val fiveTagsDS: Dataset[Row] = mysqlConn.select('id ,'rule).where("pid=4")
// 獲取出id 和 rule
// 將FiveTagsDS 封裝成樣例類TagRule
val fiveTageList: List[TagRule] = fiveTagsDS.map(row => {
// row 是一條數據
// 獲取出id 和 rule
val id: Int = row.getAs("id").toString.toInt
val rule: String = row.getAs("rule").toString
// 封裝樣例類
TagRule(id,rule)
}).collectAsList() // 將DataSet轉換成util.List[TagRule] 這個類型遍歷時無法獲取id,rule數據
.asScala.toList // 將util.List轉換成list 需要隱式轉換 import scala.collection.JavaConverters._
// for(a<- fiveTageList){
// println(a.id+" "+a.rule)
// }
//5 1
//6 2
// 5. 根據mysql數據中的四級標籤, 讀取hbase數據
// 若使用hbase 客戶端讀取效率較慢,將hbase作爲【數據源】,讀取效率較快
val hbaseDatas: DataFrame = spark.read.format("com.czxy.tools.HBaseDataSource")
// hbaseMeta.zkHosts 就是 192.168.10.20 和 下面是兩種不同的寫法
.option("zkHosts",hbaseMeta.zkHosts)
.option(HBaseMeta.ZKPORT, hbaseMeta.zkPort)
.option(HBaseMeta.HBASETABLE, hbaseMeta.hbaseTable)
.option(HBaseMeta.FAMILY, hbaseMeta.family)
.option(HBaseMeta.SELECTFIELDS, hbaseMeta.selectFields)
.load()
// 展示一些數據
hbaseDatas.show(5)
/*
+---+------+
| 1| 2|
| 10| 2|
|100| 2|
|101| 1|
|102| 2|
*/
// 6 標籤匹配
// 根據五級標籤數據和hbase數據進行標籤匹配 得到最終的標籤
// 編寫udf函數 例如輸入是1,2 返回不同性別對應的id值5或者6
val GetTagId: UserDefinedFunction = udf((gender: String) => {
// 設置標籤默認值
var id: Int = 0
// 遍歷五級標籤
for (ruleOb <- fiveTageList) {
// 當用戶數據的gender與五級標籤的id相等
// 那麼返回五級標籤的id
if (gender == ruleOb.rule) {
id = ruleOb.id
}
}
id
})
// 標籤匹配
val userTags: DataFrame = hbaseDatas.select('id.as("userId"),GetTagId('gender).as("tagsId"))
// 輸出查看效果
userTags.show()
/*
+------+------+
|userId|tagsId|
+------+------+
| 1| 6|
| 10| 6|
| 100| 6|
*/
//7 將最終的標籤寫入Hbase
userTags.write.format("com.czxy.tools.HBaseDataSource")
.option("zkHosts", hbaseMeta.zkHosts)
.option(HBaseMeta.ZKPORT, hbaseMeta.zkPort)
.option(HBaseMeta.HBASETABLE,"test")
.option(HBaseMeta.FAMILY, "detail")
.option(HBaseMeta.SELECTFIELDS, "userId,tagsId")
.save()
}
//將mysql中的四級標籤的rule 封裝成HBaseMeta
//方便後續使用的時候方便調用
def toHBaseMeta(KVMap: Map[String, String]): HBaseMeta = {
//開始封裝
HBaseMeta(KVMap.getOrElse("inType",""),
KVMap.getOrElse("zkHosts",""),
KVMap.getOrElse("zkPort",""),
KVMap.getOrElse("hbaseTable",""),
KVMap.getOrElse("family",""),
KVMap.getOrElse("selectFields",""),
KVMap.getOrElse("rowKey","")
)
}
}
小結
本篇博客主要爲大家提供了匹配型標籤如何進行開發的一個步驟流程。每一步對應的源碼也都有詳細的註釋,相信有一定大數據基礎的朋友是能夠看懂的。後續博主會更新其他類型標籤開發的博客,敬請期待🚀
如果以上過程中出現了任何的紕漏錯誤,煩請大佬們指正😅
受益的朋友或對大數據技術感興趣的夥伴記得點贊關注支持一波🙏