电影推荐系统-整体总结(二)加载数据(数据库+搜索服务器)
一、Scala代码实现
1.含有自定义数据类--Model.scala
package test /** * @Author : ASUS and xinrong * @Version : 2020/9/4 * 数据格式转换类 * ---------------电影表------------------------ * 1 * Toy Story (1995) * * 81 minutes * March 20, 2001 * 1995 * English * Adventure|Animation|Children|Comedy|Fantasy * Tom Hanks|Tim Allen|Don Rickles|Jim Varney|Wallace Shawn|John Ratzenberger|Annie Potts|John Morris|Erik von Detten|Laurie Metcalf|R. Lee Ermey|Sarah Freeman|Penn Jillette|Tom Hanks|Tim Allen|Don Rickles|Jim Varney|Wallace Shawn * John Lasseter */ case class Movie(val mid:Int,val name:String,val descri:String, val timelong:String,val cal_issue:String,val shoot:String, val language:String,val geners:String,val actors:String ,val directors:String) /** * -----用户对电影的评分数据集-------- * 1,31,2.5,1260759144 */ case class Rating(val uid:Int,val mid:Int,val score:Double,val timestamp:Int) /** * --------用户对电影的标签数据集-------- * 15,339,sandra 'boring' bullock,1138537770 */ case class Tag(val uid:Int,val mid:Int,val tag:String,val timestamp: Int) /** * * MongoDB配置对象 * @param uri * @param db */ case class MongoConfig(val uri:String,val db:String) /** * ES配置对象 * @param httpHosts * @param transportHosts:保存的是所有ES节点的信息 * @param clusterName * @param index */ case class EsConfig(val httpHosts:String,val transportHosts:String,val index:String,val clusterName:String)
2.DataLoader.scala
package test import java.net.InetAddress import com.mongodb.casbah.commons.MongoDBObject import com.mongodb.casbah.{MongoClient, MongoClientURI} import org.apache.spark.SparkConf import org.apache.spark.sql.{DataFrame, SparkSession} import org.elasticsearch.action.admin.indices.create.CreateIndexRequest import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest import org.elasticsearch.common.settings.Settings import org.elasticsearch.common.transport.InetSocketTransportAddress import org.elasticsearch.transport.client.PreBuiltTransportClient /** * @Author : ASUS and xinrong * @Version : 2020/9/4 * * Spark SQL--初始化数据 * Spark SQL数据导入MongoDB */ object DataLoader { //---------- MongoDB 中的集合 Collection ------------------- // Moive在MongoDB中的Collection名称【表】 val MOVIES_COLLECTION_NAME = "Movie" // Rating在MongoDB中的Collection名称【表】 val RATING_COLLECTION_NAME = "Rating" // Tag在MongoDB中的Collection名称【表】 val TAGS_COLLECTION_NAME = "Tag" //-------ES 正则的使用--------------------------------------- //ES TYPE的名称 val ES_TAG_TYPE_NAME = "Movie" //表名 //用正则表达式表示ES的port信息 //一个普通的字符串.r之后就变成了正则表达式 //(.+)-->任意多个字符 //(\\d+)-->数字有多位 val ES_HOST_PORT_REGEX = "(.+):(\\d+)".r def main(args: Array[String]): Unit = { //封装各个文件路径 val DATAFILE_MOVIES = "D:\\tmp_files\\reco_data\\small\\movies.csv" val DATAFILE_RATINGS = "D:\\tmp_files\\reco_data\\small\\ratings.csv" val DATAFILE_TAGS = "D:\\tmp_files\\reco_data\\small\\tags.csv" val params = scala.collection.mutable.Map[String, String]() params += "spark.core" -> "local[*]" //---------------- 关于 MongoDB 的声明 ------------------------- params += "mongodb.uri" -> "mongodb://192.168.212.21:27017/recom" params += "mongodb.db" -> "recom" //库名-recom // 定义mongoDB配置对象-隐式对象: implicit val mongoConfig = new MongoConfig(params("mongodb.uri"), params("mongodb.db")) //----------------- 关于ES的声明 -------------------------------- params += "es.httpHosts" -> "192.168.212.21:9200" //外网访问 params += "es.transportHosts" -> "192.168.212.21:9300" //内网访问 params += "es.index" -> "recom" params += "es.cluster.name" -> "my-application" //定义ES配置对象(隐式) implicit val esConfig = new EsConfig(params("es.httpHosts"), params("es.transportHosts"), params("es.index"), params("es.cluster.name")) //一、声明Spark环境 //1.config--对"local"做一层封装: val sparkConf = new SparkConf().setAppName("DataLoader").setMaster(params("spark.core")) //2.SparkSession val sparkSession = SparkSession.builder().config(sparkConf).getOrCreate() //二、加载数据集 //1.声明各个数据所在路径(单独拿出来的原因:生产和测试时的路径通常不同,将各个文件的路径放在一起便于修改) val moviesRDD = sparkSession.sparkContext.textFile(DATAFILE_MOVIES) val ratingsRDD = sparkSession.sparkContext.textFile(DATAFILE_RATINGS) val tagsRDD = sparkSession.sparkContext.textFile(DATAFILE_TAGS) //------------------------------------ MongoDB-数据准备阶段 --------------------------------------------------- //2.将RDD转换成DataFrame--使用SparkSQL比较好的特性(读入/写出数据的时候很方便) //知识点参考:https://www.cnblogs.com/liuxinrong/articles/13393309.html // https://www.cnblogs.com/liuxinrong/articles/13332014.html //1)import 导入隐式转换 import sparkSession.sqlContext.implicits._ //2)关联数据和Schema //注意:movie数据信息需要用"\^"分割行数据 val movieDF = moviesRDD.map { line => val strings = line.split("\\^") //定义Schema--通过case class方式(trim是将前后的空格去掉) Movie(strings(0).trim.toInt, strings(1).trim, strings(2).trim, strings(3).trim, strings(4).trim, strings(5).trim, strings(6).trim, strings(7).trim, strings(8).trim, strings(9).trim) }.toDF() //RDD转换成DF,便于使用Spark SQL操作DataFrame val ratingDF = ratingsRDD.map { line => val strings = line.split(",") Rating(strings(0).trim.toInt, strings(1).trim.toInt, strings(2).trim.toDouble, strings(3).toInt) }.toDF() val tagDF = tagsRDD.map { line => val strings = line.split(",") Tag(strings(0).trim.toInt, strings(1).trim.toInt, strings(2), strings(3).trim.toInt) }.toDF() // movieOF.show() //默认最多展示20行数据 // ratingOF.show(2) //只展示2行结果数据 // tagOF.show() //--------------------------------------- 写入MongoDB --------------------------------------------------------------- //三、将数据写入到MongoDB storeDataInMongo(movieDF, ratingDF, tagDF) //------------------------------------ ElasticSearch-数据准备阶段 --------------------------------------------------- //将movie表和tag表(这两个表中含有文字类的数据)进行合并之后再存入ES //因为movie和tag表需要相交,所以直接随手就将他们缓存起来 movieDF.cache() tagDF.cache() // 需要引入内置的函数库 import org.apache.spark.sql.functions._ //将tag这一列用分隔符“|”分隔开,再起一个新的名字-叫tags //tag的聚合-agg() //concat_ws()用来做分隔的 //collect_set()是将指定的列转换成一行的形式,同时进行去重(collect_list()也是将列转换成行,但就没有去重这个功能) val tagCollectDF = tagDF.groupBy("mid").agg(concat_ws("|", collect_set("tag")).as("tags")) //以mid为聚合键做左连接 val esMovieTagDF = movieDF.join(tagCollectDF, Seq("mid", "mid"), "left") .select("mid", "name", "descri", "timelong", "cal_issue", "shoot", "language", "genres", "actors", "directors", "tags") //展示一下Join之后的表 // esMovieDF.show() // --------------------------------------- 写入ElasticSearch -------------------------------------- //自定义写入ES的方法 storeDataInES(esMovieTagDF) //使用(cache())完之后最好将他们unpersist()掉 movieDF.unpersist() tagDF.unpersist() //关闭Spark sparkSession.close() } /** * MongoDB方法 * 隐式参数:mongoConfig */ def storeDataInMongo(moviesDF: DataFrame, ratingsDF: DataFrame, tagsDF: DataFrame)(implicit mongoConfig: MongoConfig): Unit = { //1.创建到MongoDB的连接去操作MongoDB--用到的是传入的隐式参数 //mongoURI是MongoDB的地址 val mongoClient = MongoClient(MongoClientURI(mongoConfig.uri)) //对于MongoDB的删集合操作--删除某个库下的集合 //MongoDB的驱动会自动创建Collection(集合),这个和ES不同 mongoClient(mongoConfig.db)(MOVIES_COLLECTION_NAME).dropCollection() mongoClient(mongoConfig.db)(RATING_COLLECTION_NAME).dropCollection() mongoClient(mongoConfig.db)(TAGS_COLLECTION_NAME).dropCollection() //2.写入MongoDB moviesDF.write .option("uri", mongoConfig.uri) //MongoDB的地址 .option("collection", MOVIES_COLLECTION_NAME) //要写入的集合名 .mode("overwrite") //采用overwrite的写入方式可以不用先删除表格,先删除(双重保险)的话也可以 .format("com.mongodb.spark.sql") .save() //保存 ratingsDF.write .option("uri", mongoConfig.uri) .option("collection", RATING_COLLECTION_NAME) .mode("overwrite") .format("com.mongodb.spark.sql") .save() tagsDF.write .option("uri", mongoConfig.uri) .option("collection", TAGS_COLLECTION_NAME) .mode("overwrite") .format("com.mongodb.spark.sql") .save() //3.创建mongodb索引--写完数据之后就创建索引 mongoClient(mongoConfig.db)(MOVIES_COLLECTION_NAME).createIndex(MongoDBObject("mid" -> 1)) //针对mid列创建索引 mongoClient(mongoConfig.db)(RATING_COLLECTION_NAME).createIndex(MongoDBObject("uid" -> 1)) //针对mid列和uid列创建索引 mongoClient(mongoConfig.db)(RATING_COLLECTION_NAME).createIndex(MongoDBObject("mid" -> 1)) mongoClient(mongoConfig.db)(TAGS_COLLECTION_NAME).createIndex(MongoDBObject("mid" -> 1)) //针对mid列和uid列创建索引 mongoClient(mongoConfig.db)(TAGS_COLLECTION_NAME).createIndex(MongoDBObject("uid" -> 1)) //4.关闭MongoDB的连接 mongoClient.close() } /** * 将数据写入ElasticSearch库的方法 * 隐式参数:esConfig(定义ES的配置,并将其以隐式参数的形式传入进来) */ def storeDataInES(esMovieTagDF: DataFrame)(implicit esConfig: EsConfig): Unit = { //1.给要操作的Index取名称--库名 val indexName = esConfig.index //库名-"recom" //2.连接ES的配置--集群名 val settings = Settings.builder().put("cluster.name", esConfig.clusterName).build() //3.创建连接ES的客户端 val esClient = new PreBuiltTransportClient(settings) //4.设置ES集群的IP(集群解析) //ES对象中的TransportHost属性保存所有ES节点信息, // 以下方法将所有节点的信息遍历出来 //"192.168.109.141:9300,192.168.109.142:9300,192.168.109.143:9300" esConfig.transportHosts.split(",") .foreach { //Scala中正则表达式的用法:case+正则表达式 //192.168.212.21:9300 case ES_HOST_PORT_REGEX(host: String, port: String) => esClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port.toInt)) } //先判断,如果Index已经存在,就先删除 if (esClient.admin().indices().exists(new IndicesExistsRequest(indexName)).actionGet().isExists) { esClient.admin().indices().delete(new DeleteIndexRequest(indexName)) } //5.创建Index--库 esClient.admin().indices().create(new CreateIndexRequest(indexName)) //6.创建Type--表 //将indexName--库名"recom"和另外一个变量--表名"Movie"拼到一起 val movieTypeName = s"$indexName/$ES_TAG_TYPE_NAME" //失效时间为100秒 val movieOption = Map("es.nodes" -> esConfig.httpHosts, "es.http.timeout" -> "100m", "es.mappting.id" -> "mid") //7.写数据 esMovieTagDF.write .options(movieOption) .mode("overwrite") .format("org.elasticSearch.spark.sql") .save(movieTypeName) } }
二、涉及到的存储信息图解

浙公网安备 33010602011771号