标签系统:
在用户画像的标签系统中,每个标签模型开发(给每个用户或每个商品打每个标签),必须先在标签管理平台注册(新建标签)业务标签(4级标签)、属性标签(5级标签)注册以后,每个标签(业务标签)对应的属性值(属性标签)都有对应标签标识符:tagName、属性标签ID,标识每个标签,将会打到每个用户或商品上,标识此用户或商品的具体标签每个业务标签对应一个模型,就是一个SparkApplication程序运行此应用程序可以给用户或商品打标签;在模型表中记录标签对应的模型关系,以及模型运行时参数信息。
用途:
标签系统的应用非常广泛,具体表现在如下几个方面:
- 内容描述:标签用于描述网站上的内容,帮助用户快速理解内容的主题或性质。
- 分类和过滤:用户可以通过标签对内容进行分类和过滤,从而更容易地找到与特定主题或兴趣相关的内容。
- 标签云和标签树:一些标签系统可以生成标签云或标签树,以更直观地展示标签之间的关系和重要性。标签云通常按照标签的相对重要性以不同的字体大小或样式呈现,而标签树则展示了标签的层次结构。
- 用户画像构建:在标签系统中,用户的各种特征和偏好可以通过标签来表示,从而构建出用户画像。这有助于企业更好地了解用户,实现精细化经营。
每个标签模型开发时,四种主要就是4个步骤:获取标签数据、获取业务数据、打标签、保存标签数据,只是在打标签这个步骤代码不一样以外,其他步骤代码基本上一致或者稍作修改,对标签模型代码进行重构,开发抽象类AbstractModel,既减少了代码的冗余,也使得开发的效率更高,提高了代码的复用性。
创建标签模型的思路:
第一步:使用设计模式:模板方法设计模式(TemplateParttern)
思想:属于一种类继承设计模式,父类(基类)和子类
基类(父类)中有2类方法:
(1)基本方法,可以是具体方法,也可以是抽象方法(给子类实现)
完成每个小的业务功能,比如针对标签模型应用程序来说,
创建SpakrSession实例对象
关闭SparkSession实例对象
加载标签数据
加载业务数据
模板方法
(2)规定基本方法执行顺序
抽象类AbstractModel的代码如下:
import com.rison.tag.config.ModelConfig
import com.rison.tag.meta.{HBaseMata, HBaseMeta}
import com.rison.tag.tools.HBaseTools
import com.rison.tag.utils.SparkUtils
import org.apache.spark.internal.Logging
import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession}
import org.apache.spark.storage.StorageLevel
/**
* 所有标签模型的基类,必须继承此类,实现doTag方法,计算标签
*/
abstract class AbstractModel(modelName: String, modelType: ModelType) extends Logging {
//设置spark应用程序运行的用户:root,默认情况下为当前系统用户
System.setProperty("user.name", "root")
System.setProperty("HADOOP_USER_NAME", "root")
//变量声明
var spark: SparkSession = _
//初始化 构建SparkSession 实例对象
def init(isHive: Boolean): Unit = {
spark = SparkUtils.createSparkSession(this.getClass, isHive)
}
//准备标签数据: 依据标签ID从mysql数据库表tal_basic_tag获取标签数据
def getTagData(tagId: Long): DataFrame = {
spark.read
.format("jdbc")
.option("driver", ModelConfig.MYSQL_JDBC_DRIVER)
.option("url", ModelConfig.MYSQL_JDBC_URL)
.option("dbtable", ModelConfig.tagTable(tagId))
.option("user", ModelConfig.MYSQL_JDBC_USERNAME)
.option("password", ModelConfig.MYSQL_JDBC_PASSWORD)
.load()
}
//业务数据: 依据业务标签规则rule,从数据源获取业务数据
def getBusinessData(tagDF: DataFrame): DataFrame = {
import tagDF.sparkSession.implicits._
//4级标签规则rule
val tagRule: String = tagDF
.filter($"level" === 4)
.head()
.getAs[String]("rule")
logInfo(s"=== 业务标签数据规则: {$tagRule}")
//解析标签规则,先按照换行\n符分割,再按照等号=分割
/**
* inType=hbase
* zkHosts=bigdata-cdh01.itcast.cn
* zkPort=2181
* hbaseTable=tbl_tag_users
* family=detail
* selectFieldNames=id,gender
*/
val ruleMap: Map[String, String] = tagRule
.split("\n")
.map {
line =>
val Array(attrName, attrValue): Array[String] = line.trim.split("=")
(attrName, attrValue)
}.toMap
//依据标签规则中inType类型获取数据
var businessDF: DataFrame = null
if ("hbase".equals(ruleMap("inType").toLowerCase())) {
//规则数据封装到HBaseMeta中
val hbaseMeta: HBaseMeta = HBaseMata.getHBaseMeta(ruleMap)
//依据添加到HBase中获取业务数据
// businessDF = HBaseTools.read(
// spark, hbaseMeta.zkHosts, hbaseMeta.zkPort, hbaseMeta.hbaseTable, hbaseMeta.family, hbaseMeta.selectFieldNames.split(",").toSeq
// )
//优化
businessDF = spark.read
.format("hbase")
.option("zkHosts", hbaseMeta.zkHosts)
.option("zkPort", hbaseMeta.zkPort)
.option("hbaseTable", hbaseMeta.hbaseTable)
.option("family", hbaseMeta.family)
.option("selectFields", hbaseMeta.selectFieldNames)
.option("whereConditions", hbaseMeta.filterConditions)
.load()
} else {
//如果未获取到数据,直接抛出异常
new RuntimeException("业务标签未提供数据源信息,获取不到业务数据,无法计算标签")
}
businessDF
}
//构建标签: 依据业务数据和属性标签数据建立标签
def doTag(businessDF: DataFrame, tagDF: DataFrame): DataFrame
//保存画像标签数据至HBase表
def saveTag(modelDF: DataFrame): Unit = {
// HBaseTools.write(
// modelDF, "bigdata-cdh01.itcast.cn", "2181", "tbl_profile", "user", "userId"
// )
//优化
modelDF.write
.mode(SaveMode.Overwrite)
.format("hbase")
.option("zkHosts", ModelConfig.PROFILE_TABLE_ZK_HOSTS)
.option("zkPort", ModelConfig.PROFILE_TABLE_ZK_PORT)
.option("hbaseTable", ModelConfig.PROFILE_TABLE_NAME)
.option("family", ModelConfig.PROFILE_TABLE_FAMILY_USER)
.option("rowKeyColumn", ModelConfig.PROFILE_TABLE_ROWKEY_COL)
.save()
}
//关闭资源: 应用结束,关闭会话实例对象
def close(): Unit = {
if (null != spark) spark.stop()
}
//规定标签模型执行流程顺序
def executeModel(tagId: Long, isHive: Boolean = false): Unit = {
//初始化
init(isHive)
try {
//获取标签数据
val tagDF: DataFrame = getTagData(tagId)
tagDF.persist(StorageLevel.MEMORY_AND_DISK).count()
//获取业务数据
val businessDF: DataFrame = getBusinessData(tagDF)
//计算标签
val modelDF: DataFrame = doTag(businessDF, tagDF)
//保存标签
saveTag(modelDF)
tagDF.unpersist()
} catch {
case e: Exception => e.printStackTrace()
} finally {
close()
}
}
}
TagTools:
TagTools封装了一些标签的相关操作,尤其是doTag方法执行的时候每次都会调用TagTools中的ruleMatchTag方法将业务数据与rule进行匹配,从而给标签数据打上标签。
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.ml.linalg
import org.apache.spark.sql.expressions.UserDefinedFunction
import org.apache.spark.sql.functions.{col, udf}
import org.apache.spark.sql.{DataFrame, SparkSession}
/**
* 针对标签进行相关操作类
* 考虑到后续规则匹配类型标签开发,都涉及到规则匹配进行打标签,可以将抽象为函数,
* 封装在工具类 TagTools 中以便使用
*/
object TagTools {
/**
* 将[属性标签]数据中[规则:rule与名称:name]转换[Map集合]
*
* @param tagDF 属性标签数据
* @return Map 集合
*/
def convertMap(tagDF: DataFrame) = {
import tagDF.sparkSession.implicits._
tagDF
//获取属性标签数据
.filter($"level" === 5)
//选择标签规则rule和标签ID
.select($"rule", $"name")
//转换为DataSet
.as[(String, String)]
//转换为RDD
.rdd
//转换为Map集合
.collectAsMap().toMap
}
/**
* 依据[标签业务字段的值] 与 [标签规则] 匹配,进行打标签(userId, tagName)
*
* @param dataFrame 标签业务数据
* @param field 标签业务字段
* @param tagDF 标签模型数据
* @return 标签模型数据
*/
def ruleMatchTag(dataFrame: DataFrame, field: String, tagDF: DataFrame) = {
val spark: SparkSession = dataFrame.sparkSession
import spark.implicits._
//1 获取规则rule与tagId集合
val attrTagRuleMap: Map[String, String] = convertMap(tagDF)
//2 将Map集合数据广播出去
val attrTagRuleMapBroadcast: Broadcast[Map[String, String]] = spark.sparkContext.broadcast(attrTagRuleMap)
//3 自定义UDF函数
val field_to_tag: UserDefinedFunction = udf(
(field: String) => attrTagRuleMapBroadcast.value(field)
)
//4 计算标签,依据业务字段值获取标签ID
val modelDF: DataFrame = dataFrame
.select(
$"id".as("userId"),
field_to_tag(col(field)).as(field)
)
//5 返回计算标签数据
modelDF
}
}
第二步:属性文件配置,数据库信息和应用属性信息
(1)数据库相关配置数据,MySQL数据库(标签数据)和HBase数据库(画像标签数据
属性配置文件:config.properties
使用库:TypeSafe加载属性配置文件,编写工具类:ModelConfig屏蔽掉了一些生产环境的信息,比如说数据库的信息、用户名,密码等信息。
config.properties:
# model config
tag.model.base.path=/apps/tags/models/
# mysql config
mysql.jdbc.driver=com.mysql.jdbc.Driver
mysql.jdbc.url=jdbc:mysql://bigdata-cdh01.itcast.cn:3306/?
useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
mysql.jdbc.username=root
mysql.jdbc.password=123456
# profile table config
profile.hbase.zk.hosts=bigdata-cdh01.itcast.cn
profile.hbase.zk.port=2181
profile.hbase.zk.znode=/hbase
profile.hbase.table.name=tbl_profile
profile.hbase.table.family.user=user
profile.hbase.table.family.item=item
profile.hbase.table.family.common.col=tagIds
profile.hbase.table.rowkey.col=userId
# hdfs config
fs.defaultFS=hdfs://bigdata-cdh01.itcast.cn:8020
fs.user=root
ModelConfig:
import com.typesafe.config._
/**
* 读取配置文件信息config.properties,获取属性值
*/
object ModelConfig {
// 构建Config对象,读取配置文件
val config: Config = ConfigFactory.load("config.properties")
// Model Config
lazy val MODEL_BASE_PATH: String =
config.getString("tag.model.base.path")
// MySQL Config
lazy val MYSQL_JDBC_DRIVER: String =
config.getString("mysql.jdbc.driver")
lazy val MYSQL_JDBC_URL: String = config.getString("mysql.jdbc.url")
lazy val MYSQL_JDBC_USERNAME: String =
config.getString("mysql.jdbc.username")
lazy val MYSQL_JDBC_PASSWORD: String =
config.getString("mysql.jdbc.password")
// Basic Tag table config
def tagTable(tagId: Long): String = {
s"""
|(
|SELECT `id`,
| `name`,
| `rule`,
| `level`
|FROM `profile_tags`.`tbl_basic_tag`
|WHERE id = $tagId
|UNION
|SELECT `id`,
| `name`,
| `rule`,
| `level`
|FROM `profile_tags`.`tbl_basic_tag`
|WHERE pid = $tagId
|ORDER BY `level` ASC, `id` ASC
|) AS basic_tag
|""".stripMargin
}
// Profile table Config
lazy val PROFILE_TABLE_ZK_HOSTS: String =
config.getString("profile.hbase.zk.hosts")
lazy val PROFILE_TABLE_ZK_PORT: String =
config.getString("profile.hbase.zk.port")
lazy val PROFILE_TABLE_ZK_ZNODE: String =
config.getString("profile.hbase.zk.znode")
lazy val PROFILE_TABLE_NAME: String =
config.getString("profile.hbase.table.name")
lazy val PROFILE_TABLE_FAMILY_USER: String =
config.getString("profile.hbase.table.family.user")
lazy val PROFILE_TABLE_FAMILY_ITEM: String =
config.getString("profile.hbase.table.family.item")
lazy val PROFILE_TABLE_COMMON_COL: String =
config.getString("profile.hbase.table.family.common.col")
// 作为RowKey列名称
lazy val PROFILE_TABLE_ROWKEY_COL: String =
config.getString("profile.hbase.table.rowkey.col")
// HDFS Config
lazy val DEFAULT_FS: String = config.getString("fs.defaultFS")
lazy val FS_USER: String = config.getString("fs.user")
// Spark Application Local Mode
lazy val APP_IS_LOCAL: Boolean = config.getBoolean("app.is.local")
lazy val APP_SPARK_MASTER: String = config.getString("app.spark.master")
// Spark Application With Hive
lazy val APP_IS_HIVE: Boolean = config.getBoolean("app.is.hive")
lazy val APP_HIVE_META_STORE_URL: String = config.getString("app.hive.metastore.uris")
}
在定义了这两个类以后在生产环境中就能减少信息的泄露以及安全隐患,从而提高安全性。
总结:
构建标签模型模板使得标签在开发的过程中错误性大大降低,提高了代码的开发效率。
同时对于属性文件的配置有利于降低开发过程中的风险,也有利于屏蔽生产环境中 数据库的信息、用户名,密码等信息的错误。
(以上部分资料来自黑马程序员,自用笔记,侵删。)