【ビッグデータ実践型電子商取引レコメンドシステム】

記事ディレクトリ


第1章 プロジェクト体制の枠組み設計


第2章 ツール環境構築

  • MongoDB の最新バージョンをインストール => Ubuntu に mongodb をインストールするときに依存関係が欠落する問題を解決する
  • CentOS7 システムを使用し、ツール環境構築プロセスに従って MongoDB、Redis、Spark、Zookeeper、Flume-ng、Kafka をインストールします。

第3章 プロジェクトの作成と業務データの初期化

3.1 IDEAがMavenプロジェクトを作成する(省略)

3.2 データ読み込みの準備(手順)

3.3 MongoDBへのデータの初期化【DataLoaderデータロードモジュール】

データローダー本体実装+MongoDBへのデータ書き込み

  • 元のデータに対していくつかのサンプル クラスを定義し、SparkContext の textFile メソッドを通じてファイルからデータを読み取り、DataFrame に変換した後、Spark SQL が提供する write メソッドを使用してデータの分散挿入を実行します。
  • DataLoader/src/main/scala に新しいパッケージを作成し、com.atguigu.recommender という名前を付け、DataLoader という名前の新しい scala クラス ファイルを作成します。

プログラムのメインコード:

package com.atguigu.recommender

import com.mongodb.casbah.commons.MongoDBObject
import com.mongodb.casbah.{
    
    MongoClient, MongoClientURI}
import org.apache.spark.SparkConf
import org.apache.spark.sql.{
    
    DataFrame, SparkSession}

/**
  *
  * Product数据集
  * 3982                            商品ID
  * Fuhlen 富勒 M8眩光舞者时尚节能    商品名称
  * 1057,439,736                    商品分类ID,不需要
  * B009EJN4T2                      亚马逊ID,不需要
  * https://images-cn-4.ssl-image   商品的图片URL
  * 外设产品|鼠标|电脑/办公           商品分类
  * 富勒|鼠标|电子产品|好用|外观漂亮   商品UGC标签
  */
case class Product( productId: Int, name: String, imageUrl: String, categories: String, tags: String )

/**
  * Rating数据集
  * 4867        用户ID
  * 457976      商品ID
  * 5.0         评分
  * 1395676800  时间戳
  */
case class Rating( userId: Int, productId: Int, score: Double, timestamp: Int )

/**
  * MongoDB连接配置
  * @param uri    MongoDB的连接uri
  * @param db     要操作的db
  */
case class MongoConfig( uri: String, db: String )

object DataLoader {
    
    
  // 定义数据文件路径
  val PRODUCT_DATA_PATH = "D:\\Projects\\BigData\\ECommerceRecommendSystem\\recommender\\DataLoader\\src\\main\\resources\\products.csv"
  val RATING_DATA_PATH = "D:\\Projects\\BigData\\ECommerceRecommendSystem\\recommender\\DataLoader\\src\\main\\resources\\ratings.csv"
  // 定义mongodb中存储的表名
  val MONGODB_PRODUCT_COLLECTION = "Product"
  val MONGODB_RATING_COLLECTION = "Rating"

  def main(args: Array[String]): Unit = {
    
    
    val config = Map(
      "spark.cores" -> "local[*]",
      "mongo.uri" -> "mongodb://localhost:27017/recommender",
      "mongo.db" -> "recommender"
    )
    // 创建一个spark config
    val sparkConf = new SparkConf().setMaster(config("spark.cores")).setAppName("DataLoader")
    // 创建spark session
    val spark = SparkSession.builder().config(sparkConf).getOrCreate()

    import spark.implicits._

    // 加载数据
    val productRDD = spark.sparkContext.textFile(PRODUCT_DATA_PATH)
    val productDF = productRDD.map( item => {
    
    
      // product数据通过^分隔,切分出来
      val attr = item.split("\\^")
      // 转换成Product
      Product( attr(0).toInt, attr(1).trim, attr(4).trim, attr(5).trim, attr(6).trim )
    } ).toDF()

    val ratingRDD = spark.sparkContext.textFile(RATING_DATA_PATH)
    val ratingDF = ratingRDD.map( item => {
    
    
      val attr = item.split(",")
      Rating( attr(0).toInt, attr(1).toInt, attr(2).toDouble, attr(3).toInt )
    } ).toDF()

    implicit val mongoConfig = MongoConfig( config("mongo.uri"), config("mongo.db") )
    storeDataInMongoDB( productDF, ratingDF )

    spark.stop()
  }


  /**
  * 数据写入MongoDB
  */
  def storeDataInMongoDB( productDF: DataFrame, ratingDF: DataFrame )(implicit mongoConfig: MongoConfig): Unit ={
    
    
    // 新建一个mongodb的连接,客户端
    val mongoClient = MongoClient( MongoClientURI(mongoConfig.uri) )
    // 定义要操作的mongodb表,可以理解为 db.Product
    val productCollection = mongoClient( mongoConfig.db )( MONGODB_PRODUCT_COLLECTION )
    val ratingCollection = mongoClient( mongoConfig.db )( MONGODB_RATING_COLLECTION )

    // 如果表已经存在,则删掉
    productCollection.dropCollection()
    ratingCollection.dropCollection()

    // 将当前数据存入对应的表中
    productDF.write
      .option("uri", mongoConfig.uri)
      .option("collection", MONGODB_PRODUCT_COLLECTION)
      .mode("overwrite")
      .format("com.mongodb.spark.sql")
      .save()

    ratingDF.write
      .option("uri", mongoConfig.uri)
      .option("collection", MONGODB_RATING_COLLECTION)
      .mode("overwrite")
      .format("com.mongodb.spark.sql")
      .save()

    // 对表创建索引
    productCollection.createIndex( MongoDBObject( "productId" -> 1 ) )
    ratingCollection.createIndex( MongoDBObject( "productId" -> 1 ) )
    ratingCollection.createIndex( MongoDBObject( "userId" -> 1 ) )

    mongoClient.close()
  }
}
  • ファイアウォールの問題: mongodb に接続するときはファイアウォールをオフにする必要があります

StatisticsRecommender 統計推奨モジュール

コード部分:

package com.atguigu.statistics

import java.text.SimpleDateFormat
import java.util.Date

import org.apache.spark.SparkConf
import org.apache.spark.sql.{
    
    DataFrame, SparkSession}

case class Rating( userId: Int, productId: Int, score: Double, timestamp: Int )
case class MongoConfig( uri: String, db: String )

object StatisticsRecommender {
    
    
  // 定义mongodb中存储的表名
  val MONGODB_RATING_COLLECTION = "Rating"
  val RATE_MORE_PRODUCTS = "RateMoreProducts"
  val RATE_MORE_RECENTLY_PRODUCTS = "RateMoreRecentlyProducts"
  val AVERAGE_PRODUCTS = "AverageProducts"

  def main(args: Array[String]): Unit = {
    
    
    val config = Map(
      "spark.cores" -> "local[1]",
      "mongo.uri" -> "mongodb://localhost:27017/recommender",
      "mongo.db" -> "recommender"
    )
    // 创建一个spark config
    val sparkConf = new SparkConf().setMaster(config("spark.cores")).setAppName("StatisticsRecommender")
    // 创建spark session
    val spark = SparkSession.builder().config(sparkConf).getOrCreate()

    import spark.implicits._
    implicit val mongoConfig = MongoConfig( config("mongo.uri"), config("mongo.db") )

    // 加载数据
    val ratingDF = spark.read
      .option("uri", mongoConfig.uri)
      .option("collection", MONGODB_RATING_COLLECTION)
      .format("com.mongodb.spark.sql")
      .load()
      .as[Rating]
      .toDF()

    // 创建一张叫ratings的临时表
    ratingDF.createOrReplaceTempView("ratings")

    // TODO: 用spark sql去做不同的统计推荐
    // 1. 历史热门商品,按照评分个数统计,productId,count
    val rateMoreProductsDF = spark.sql("select productId, count(productId) as count from ratings group by productId order by count desc")
    storeDFInMongoDB( rateMoreProductsDF, RATE_MORE_PRODUCTS )

    // 2. 近期热门商品,把时间戳转换成yyyyMM格式进行评分个数统计,最终得到productId, count, yearmonth
    // 创建一个日期格式化工具
    val simpleDateFormat = new SimpleDateFormat("yyyyMM")
    // 注册UDF,将timestamp转化为年月格式yyyyMM
    spark.udf.register("changeDate", (x: Int)=>simpleDateFormat.format(new Date(x * 1000L)).toInt)
    // 把原始rating数据转换成想要的结构productId, score, yearmonth
    val ratingOfYearMonthDF = spark.sql("select productId, score, changeDate(timestamp) as yearmonth from ratings")
    ratingOfYearMonthDF.createOrReplaceTempView("ratingOfMonth")
    val rateMoreRecentlyProductsDF = spark.sql("select productId, count(productId) as count, yearmonth from ratingOfMonth group by yearmonth, productId order by yearmonth desc, count desc")
    // 把df保存到mongodb
    storeDFInMongoDB( rateMoreRecentlyProductsDF, RATE_MORE_RECENTLY_PRODUCTS )

    // 3. 优质商品统计,商品的平均评分,productId,avg
    val averageProductsDF = spark.sql("select productId, avg(score) as avg from ratings group by productId order by avg desc")
    storeDFInMongoDB( averageProductsDF, AVERAGE_PRODUCTS )

    spark.stop()
  }
  def storeDFInMongoDB(df: DataFrame, collection_name: String)(implicit mongoConfig: MongoConfig): Unit ={
    
    
    df.write
      .option("uri", mongoConfig.uri)
      .option("collection", collection_name)
      .mode("overwrite")
      .format("com.mongodb.spark.sql")
      .save()
  }
}

コード分​​析:

  • 一時テーブル -> 結果テーブル
  • UDF を登録し、タイムスタンプを年月形式 yyyyMM に変換します
spark.udf.register("changeDate", (x: Int)=>simpleDateFormat.format(new Date(x * 1000L)).toInt)

第4章 オフラインレコメンドサービスの構築

4.1 オフラインレコメンドサービス

  • オフラインレコメンドサービスは、ユーザーのすべての履歴データを統合し、設定されたオフライン統計アルゴリズムとオフラインレコメンデーションアルゴリズムを使用して結果を定期的に収集して保存します.計算された結果は一定期間内に固定され、変更の頻度は方法に依存します多くの場合、アルゴリズムはスケジュールされています。
  • オフラインレコメンデーションサービスは、主に事前にカウントおよび計算できるいくつかの指標を計算し、リアルタイムの計算とフロントエンドのビジネス応答のためのデータサポートを提供します。
  • オフライン レコメンデーション サービスは主に、統計的レコメンデーション、潜在セマンティック モデル ベースの協調フィルタリング レコメンデーション、およびコンテンツ ベースおよびアイテム CF ベースの同様のレコメンデーションに分類されます。
  • この章では主に最初の 2 つの部分を紹介しますが、コンテンツベースの推奨事項とアイテム CF の推奨事項は、全体の構造と実装が似ており、第 7 章で詳しく紹介します。

4.2 オフライン統計サービス[統計推奨モジュール]

  • recommender の下に新しいサブプロジェクト StatisticsRecommender を作成します。必要な作業は、spark、scala、mongodb の関連する依存関係を pom.xml ファイルに導入することだけです。
<dependencies>
    <!-- Spark的依赖引入 -->
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-core_2.11</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-sql_2.11</artifactId>
    </dependency>
    <!-- 引入Scala -->
    <dependency>
        <groupId>org.scala-lang</groupId>
        <artifactId>scala-library</artifactId>
    </dependency>
    <!-- 加入MongoDB的驱动 -->
    <!-- 用于代码方式连接MongoDB -->
    <dependency>
        <groupId>org.mongodb</groupId>
        <artifactId>casbah-core_2.11</artifactId>
        <version>${
    
    casbah.version}</version>
    </dependency>
    <!-- 用于Spark和MongoDB的对接 -->
    <dependency>
        <groupId>org.mongodb.spark</groupId>
        <artifactId>mongo-spark-connector_2.11</artifactId>
        <version>${
    
    mongodb-spark.version}</version>
    </dependency>
</dependencies>

resource フォルダーの下に log4j.properties を導入し、src/main/scala の下に新しい scala シングルトン オブジェクト com.atguigu.statistics.StatisticsRecommender を作成します。
同様に、最初にサンプル クラスを構築し、構成を定義し、SparkSession を作成して main() メソッドにデータをロードし、最後に Spark を閉じる必要があります。

  • 過去の人気製品の統計: すべての過去の評価データに基づいて、最も歴史的な評価を持つ製品を計算します。
    • Spark SQL を通じて評価データセットを読み取り、すべての評価の中で最も多くの評価を持つ製品を数えます。
    • 次に、大きいものから小さいものへと並べ替え、最終結果を MongoDB の RateMoreProducts データ セットに書き込みます。
  • 最近の人気製品の統計: 評価に基づいて、最近 1 か月間で最も評価の高い製品のコレクションを月単位で計算します。
    • Spark SQL を介して評価データセットを読み取り、UDF 関数を使用して評価データの時間を月に変更し、月次の製品評価の数をカウントします。
    • 統計が完了すると、データは MongoDB の RateMoreRecentlyProducts データ セットに書き込まれます。
  • 製品の平均スコア統計: 履歴データ内の製品に対するすべてのユーザーの評価に基づいて、各製品の平均スコアが定期的に計算されます。
    • Spark SQL を通じて MongDB に保存された評価データセットを読み取り、次の SQL ステートメントを実行して製品の平均スコア統計を実装します。
    • 統計が完了すると、生成された新しい DataFrame が MongoDB の AverageProducts コレクションに書き出されます。

メインコード (src/main/scala/com.atguigu.statistics/StatisticsRecommender.scala):

package com.atguigu.statistics

import java.text.SimpleDateFormat
import java.util.Date

import org.apache.spark.SparkConf
import org.apache.spark.sql.{
    
    DataFrame, SparkSession}

case class Rating( userId: Int, productId: Int, score: Double, timestamp: Int )
case class MongoConfig( uri: String, db: String )

object StatisticsRecommender {
    
    
  // 定义mongodb中存储的表名
  val MONGODB_RATING_COLLECTION = "Rating"
  val RATE_MORE_PRODUCTS = "RateMoreProducts"
  val RATE_MORE_RECENTLY_PRODUCTS = "RateMoreRecentlyProducts"
  val AVERAGE_PRODUCTS = "AverageProducts"

  def main(args: Array[String]): Unit = {
    
    
    val config = Map(
      "spark.cores" -> "local[1]",
      "mongo.uri" -> "mongodb://localhost:27017/recommender",
      "mongo.db" -> "recommender"
    )
    // 创建一个spark config
    val sparkConf = new SparkConf().setMaster(config("spark.cores")).setAppName("StatisticsRecommender")
    // 创建spark session
    val spark = SparkSession.builder().config(sparkConf).getOrCreate()

    import spark.implicits._
    implicit val mongoConfig = MongoConfig( config("mongo.uri"), config("mongo.db") )

    // 加载数据
    val ratingDF = spark.read
      .option("uri", mongoConfig.uri)
      .option("collection", MONGODB_RATING_COLLECTION)
      .format("com.mongodb.spark.sql")
      .load()
      .as[Rating]
      .toDF()

    // 创建一张叫ratings的临时表
    ratingDF.createOrReplaceTempView("ratings")

    // TODO: 【 用spark sql去做不同的统计推荐 】
    // todo: (1)历史热门商品,按照评分个数统计,productId,count
    val rateMoreProductsDF = spark.sql("select productId, count(productId) as count from ratings group by productId order by count desc")
    storeDFInMongoDB( rateMoreProductsDF, RATE_MORE_PRODUCTS )

    // todo: (2)近期热门商品,把时间戳转换成yyyyMM格式进行评分个数统计,最终得到productId, count, yearmonth
    // 创建一个日期格式化工具
    val simpleDateFormat = new SimpleDateFormat("yyyyMM")
    // 注册UDF,将timestamp转化为年月格式yyyyMM
    spark.udf.register("changeDate", (x: Int)=>simpleDateFormat.format(new Date(x * 1000L)).toInt)
    // 把原始rating数据转换成想要的结构productId, score, yearmonth
    val ratingOfYearMonthDF = spark.sql("select productId, score, changeDate(timestamp) as yearmonth from ratings")
    ratingOfYearMonthDF.createOrReplaceTempView("ratingOfMonth")
    val rateMoreRecentlyProductsDF = spark.sql("select productId, count(productId) as count, yearmonth from ratingOfMonth group by yearmonth, productId order by yearmonth desc, count desc")
    // 把df保存到mongodb
    storeDFInMongoDB( rateMoreRecentlyProductsDF, RATE_MORE_RECENTLY_PRODUCTS )

    // todo: (3)优质商品统计,商品的平均评分,productId,avg
    val averageProductsDF = spark.sql("select productId, avg(score) as avg from ratings group by productId order by avg desc")
    storeDFInMongoDB( averageProductsDF, AVERAGE_PRODUCTS )

    spark.stop()
  }

  // TODO: 【 保存到MongoDB数据库 】
  def storeDFInMongoDB(df: DataFrame, collection_name: String)(implicit mongoConfig: MongoConfig): Unit ={
    
    
    df.write
      .option("uri", mongoConfig.uri)
      .option("collection", collection_name)
      .mode("overwrite")
      .format("com.mongodb.spark.sql")
      .save()
  }
}

4.3 潜在意味モデルに基づく協調フィルタリング推奨 [LFM オフライン推奨モジュール]

  • このプロジェクトでは、協調フィルタリング アルゴリズムとして ALS を使用し、MongoDB のユーザー評価テーブルに基づいてオフライン ユーザー製品推奨リストと製品類似性マトリックスを計算します。

4.3.1 ユーザー製品推奨リスト

  • ALS によってトレーニングされたモデルは、現在のすべてのユーザー製品の推奨リストを計算するために使用されます。主なアイデアは次のとおりです。
    • userId と productId のデカルト積により、(userId, productId) のタプルが生成されます
    • モデルを通じて (userId、productId) に対応する評価を予測します。
    • 予測結果を予測スコア順に並べ替えます。
    • 現在のユーザーの推奨リストとして最高スコアを持つ K 製品を返します。
  • 最終的に生成されるデータ構造は次のとおりです。 MongoDB の UserRecs テーブルにデータを保存します。
    ここに画像の説明を挿入します
  • 新しいレコメンダー サブプロジェクト OfflineRecommender を作成し、spark、scala、mongo、jblas の依存関係を導入します。
<dependencies>

    <dependency>
        <groupId>org.scalanlp</groupId>
        <artifactId>jblas</artifactId>
        <version>${
    
    jblas.version}</version>
    </dependency>

    <!-- Spark的依赖引入 -->
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-core_2.11</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-sql_2.11</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-mllib_2.11</artifactId>
    </dependency>
    <!-- 引入Scala -->
    <dependency>
        <groupId>org.scala-lang</groupId>
        <artifactId>scala-library</artifactId>
    </dependency>

    <!-- 加入MongoDB的驱动 -->
    <!-- 用于代码方式连接MongoDB -->
    <dependency>
        <groupId>org.mongodb</groupId>
        <artifactId>casbah-core_2.11</artifactId>
        <version>${
    
    casbah.version}</version>
    </dependency>
    <!-- 用于Spark和MongoDB的对接 -->
    <dependency>
        <groupId>org.mongodb.spark</groupId>
        <artifactId>mongo-spark-connector_2.11</artifactId>
        <version>${
    
    mongodb-spark.version}</version>
    </dependency>
</dependencies>
  • サンプル クラスの構築、構成の宣言、SparkSession の作成という同じ前の手順を実行した後、データをロードしてモデルの計算を開始できます。

コアコードは次のとおりです: src/main/scala/com.atguigu.offline/OfflineRecommender.scala

case class ProductRating(userId: Int, productId: Int, score: Double, timestamp: Int)

case class MongoConfig(uri:String, db:String)

// 标准推荐对象,productId,score
case class Recommendation(productId: Int, score:Double)

// 用户推荐列表
case class UserRecs(userId: Int, recs: Seq[Recommendation])

// 商品相似度(商品推荐)
case class ProductRecs(productId: Int, recs: Seq[Recommendation])

object OfflineRecommmeder {
    
    

  // 定义常量
  val MONGODB_RATING_COLLECTION = "Rating"

  // 推荐表的名称
  val USER_RECS = "UserRecs"
  val PRODUCT_RECS = "ProductRecs"

  val USER_MAX_RECOMMENDATION = 20

  def main(args: Array[String]): Unit = {
    
    
    // 定义配置
    val config = Map(
      "spark.cores" -> "local[*]",
      "mongo.uri" -> "mongodb://localhost:27017/recommender",
      "mongo.db" -> "recommender"
    )

    // 创建spark session
    val sparkConf = new SparkConf().setMaster(config("spark.cores")).setAppName("OfflineRecommender")
    val spark = SparkSession.builder().config(sparkConf).getOrCreate()

    implicit val mongoConfig = MongoConfig(config("mongo.uri"),config("mongo.db"))

    import spark.implicits._
	//读取mongoDB中的业务数据
	val ratingRDD = spark
	.read
	.option("uri",mongoConfig.uri)
	.option("collection",MONGODB_RATING_COLLECTION)
	.format("com.mongodb.spark.sql")
	.load()
	.as[ProductRating]
	.rdd
	.map(rating=> (rating.userId, rating.productId, rating.score)).cache()
	//用户的数据集 RDD[Int]
	val userRDD = ratingRDD.map(_._1).distinct()
	val prodcutRDD = ratingRDD.map(_._2).distinct()
	
	//创建训练数据集
	val trainData = ratingRDD.map(x => Rating(x._1,x._2,x._3))
	// rank 是模型中隐语义因子的个数, iterations 是迭代的次数, lambda 是ALS的正则化参
	val (rank,iterations,lambda) = (50, 5, 0.01)
	// 调用ALS算法训练隐语义模型
	val model = ALS.train(trainData,rank,iterations,lambda)
	
	//计算用户推荐矩阵
	val userProducts = userRDD.cartesian(productRDD)
	// model已训练好,把id传进去就可以得到预测评分列表RDD[Rating] (userId,productId,rating)
	val preRatings = model.predict(userProducts)
	
	val userRecs = preRatings
	.filter(_.rating > 0)
	.map(rating => (rating.user,(rating.product, rating.rating)))
	.groupByKey()    
	.map{
    
    
	case (userId,recs) => UserRecs(userId,recs.toList.sortWith(_._2 >
	_._2).take(USER_MAX_RECOMMENDATION).map(x => Recommendation(x._1,x._2)))
	}.toDF()
	
	userRecs.write
	.option("uri",mongoConfig.uri)
	.option("collection",USER_RECS)
	.mode("overwrite")
	.format("com.mongodb.spark.sql")
	.save()
	
	//TODO:计算商品相似度矩阵
	
	// 关闭spark
	spark.stop()
	}
}

4.3.2 製品類似性マトリックス

  • 製品類似性マトリックスは ALS を通じて計算され、現在の製品の類似製品をクエリし、リアルタイム推奨システムを提供するために使用されます。

  • オフライン計算用の ALS アルゴリズムでは、アルゴリズムは最終的にユーザーと製品の最終的な特徴行列を生成します。これは、ユーザー特徴行列を表す U(mxk) 行列で、各ユーザーは k 個の特徴によって記述されます。V(nxk)アイテム特徴行列を表す行列 ) 行列、各アイテムも k 個の特徴によって記述されます。

  • V(nxk) は商品の特徴行列を表し、各行は k 次元のベクトルです。各次元の特徴の意味はわかりませんが、k 次元の数学的ベクトルは、対応する商品の特徴を表します。行。

  • したがって、各商品は、V(nxk) の各行の<t 1 ,t 2 ,t 3 ,…> ベクトルを使用してその特徴を表すため、任意の 2 つの商品 p: 特徴ベクトルは V p =< t p1 ,tとなります。 p2 , t p3 ,…,t pk >, 積 q: 特徴ベクトル間の類似度 sim(p,q) は V q =< t q1 ,t q2 ,t q3 ,…,t qk > はコサイン値を使用できます合計の次の値を表します。
    ここに画像の説明を挿入します

  • データセット内の任意の 2 つのアイテム間の類似度は、数式で計算できます。アイテム間の類似度は、基本的に一定期間にわたって固定値です。最終的に生成されたデータは、MongoDB の ProductRecs テーブルに保存されます。
    ここに画像の説明を挿入します

//计算商品相似度矩阵
//获取商品的特征矩阵,数据格式 RDD[(scala.Int, scala.Array[scala.Double])]
val productFeatures = model.productFeatures.map{
    
    case (productId,features) =>
  (productId, new DoubleMatrix(features))
}

// 计算笛卡尔积并过滤合并
val productRecs = productFeatures.cartesian(productFeatures)
  .filter{
    
    case (a,b) => a._1 != b._1}  
  .map{
    
    case (a,b) =>
    val simScore = this.consinSim(a._2,b._2) // 求余弦相似度
    (a._1,(b._1,simScore))
  }.filter(_._2._2 > 0.6)    
  .groupByKey()             
  .map{
    
    case (productId,items) =>
    ProductRecs(productId,items.toList.map(x => Recommendation(x._1,x._2)))
  }.toDF()

productRecs
  .write
  .option("uri", mongoConfig.uri)
  .option("collection",PRODUCT_RECS)
  .mode("overwrite")
  .format("com.mongodb.spark.sql")
  .save()
  
//计算两个商品之间的余弦相似度
def consinSim(product1: DoubleMatrix, product2:DoubleMatrix) : Double ={
    
    
  product1.dot(product2) / ( product1.norm2()  * product2.norm2() )
}

4.3.3 モデルの評価とパラメータの選択

  • 上記のモデルを訓練する過程で、潜在意味モデルのランク、反復、ラムダの 3 つのパラメーターを直接与えました。
  • 私たちのモデルの場合、これは必ずしも最適なパラメーターの選択ではないため、モデルを評価する必要があります。
  • 一般的なアプローチは、予測スコアと実際のスコアの間の誤差を調べる二乗平均平方根誤差 (RMSE) を計算することです。
    ここに画像の説明を挿入します
  • RMSE を使用すると、パラメーター値を複数回調整することで、最小の RMSE を持つグループをモデルの最適化の選択肢として選択できます。

コア コード: scala/com.atguigu.offline/ に新しいシングルトン オブジェクト ALSTrainer を作成します。

def main(args: Array[String]): Unit = {
    
    
  val config = Map(
    "spark.cores" -> "local[*]",
    "mongo.uri" -> "mongodb://localhost:27017/recommender",
    "mongo.db" -> "recommender"
  )
  //创建SparkConf
  val sparkConf = new SparkConf().setAppName("ALSTrainer").setMaster(config("spark.cores"))
  //创建SparkSession
  val spark = SparkSession.builder().config(sparkConf).getOrCreate()

  val mongoConfig = MongoConfig(config("mongo.uri"),config("mongo.db"))

  import spark.implicits._

  //加载评分数据
  val ratingRDD = spark
    .read
    .option("uri",mongoConfig.uri)
    .option("collection",OfflineRecommender.MONGODB_RATING_COLLECTION)
    .format("com.mongodb.spark.sql")
    .load()
    .as[ProductRating]
    .rdd
    .map(rating => Rating(rating.userId,rating.productId,rating.score)).cache()

  // 将一个RDD随机切分成两个RDD,用以划分训练集和测试集
  val splits = ratingRDD.randomSplit(Array(0.8, 0.2))

  val trainingRDD = splits(0)
  val testingRDD = splits(1)

  //输出最优参数
  adjustALSParams(trainingRDD, testingRDD)

  //关闭Spark
  spark.close()
}

  • AdjustALSParams メソッドはモデル評価の中核であり、トレーニング データとテスト データのセットを入力し、最小 RMSE を計算するパラメーターのセットを出力します。
  • コードは次のように実装されます。
// 输出最终的最优参数
def adjustALSParams(trainData:RDD[Rating], testData:RDD[Rating]): Unit ={
    
    
// 这里指定迭代次数为5,rank和lambda在几个值中选取调整
  val result = for(rank <- Array(100,200,250); lambda <- Array(1, 0.1, 0.01, 0.001))
    yield {
    
    
      val model = ALS.train(trainData,rank,5,lambda)
      val rmse = getRMSE(model, testData)
      (rank,lambda,rmse)
    }
  // 按照rmse排序
  println(result.sortBy(_._3).head)
}
  • RMSE を計算する関数 getRMSE コードは次のように実装されます。
def getRMSE(model:MatrixFactorizationModel, data:RDD[Rating]):Double={
    
    
  val userProducts = data.map(item => (item.user,item.product))
  val predictRating = model.predict(userProducts)
val real = data.map(item => ((item.user,item.product),item.rating))
  val predict = predictRating.map(item => ((item.user,item.product),item.rating))
  // 计算RMSE
  sqrt(
    real.join(predict).map{
    
    case ((userId,productId),(real,pre))=>
      // 真实值和预测值之间的差
      val err = real - pre
      err * err
    }.mean()
  )
}
  • コードを実行して、現在のデータに最適なモデル パラメーターを取得します。

コード本体:

package com.atguigu.offline

import org.apache.spark.SparkConf
import org.apache.spark.mllib.recommendation.{
    
    ALS, Rating}
import org.apache.spark.sql.SparkSession
import org.jblas.DoubleMatrix

case class ProductRating( userId: Int, productId: Int, score: Double, timestamp: Int )
case class MongoConfig( uri: String, db: String )

// 定义标准推荐对象
case class Recommendation( productId: Int, score: Double )
// 定义用户的推荐列表
case class UserRecs( userId: Int, recs: Seq[Recommendation] )
// 定义商品相似度列表
case class ProductRecs( productId: Int, recs: Seq[Recommendation] )

object OfflineRecommender {
    
    
  // 定义mongodb中存储的表名
  val MONGODB_RATING_COLLECTION = "Rating"

  val USER_RECS = "UserRecs"
  val PRODUCT_RECS = "ProductRecs"
  val USER_MAX_RECOMMENDATION = 20

  def main(args: Array[String]): Unit = {
    
    
    val config = Map(
      "spark.cores" -> "local[*]",
      "mongo.uri" -> "mongodb://localhost:27017/recommender",
      "mongo.db" -> "recommender"
    )
    // 创建一个spark config
    val sparkConf = new SparkConf().setMaster(config("spark.cores")).setAppName("OfflineRecommender")
    // 创建spark session
    val spark = SparkSession.builder().config(sparkConf).getOrCreate()

    import spark.implicits._
    implicit val mongoConfig = MongoConfig( config("mongo.uri"), config("mongo.db") )

    // 加载数据
    val ratingRDD = spark.read
      .option("uri", mongoConfig.uri)
      .option("collection", MONGODB_RATING_COLLECTION)
      .format("com.mongodb.spark.sql")
      .load()
      .as[ProductRating]
      .rdd
      .map(
        rating => (rating.userId, rating.productId, rating.score)
      ).cache()

    // 提取出所有用户和商品的数据集
    val userRDD = ratingRDD.map(_._1).distinct()
    val productRDD = ratingRDD.map(_._2).distinct()

    // 核心计算过程
    // 1. 训练隐语义模型
    val trainData = ratingRDD.map(x=>Rating(x._1,x._2,x._3))
    // 定义模型训练的参数,rank隐特征个数,iterations迭代词数,lambda正则化系数
    val ( rank, iterations, lambda ) = ( 5, 10, 0.01 )
    val model = ALS.train( trainData, rank, iterations, lambda )

    // 2. 获得预测评分矩阵,得到用户的推荐列表
    // 用userRDD和productRDD做一个笛卡尔积,得到空的userProductsRDD表示的评分矩阵
    val userProducts = userRDD.cartesian(productRDD)
    val preRating = model.predict(userProducts)

    // 从预测评分矩阵中提取得到用户推荐列表
    val userRecs = preRating.filter(_.rating>0)
      .map(
        rating => ( rating.user, ( rating.product, rating.rating ) )
      )
      .groupByKey()
      .map{
    
    
        case (userId, recs) =>
          UserRecs( userId, recs.toList.sortWith(_._2>_._2).take(USER_MAX_RECOMMENDATION).map(x=>Recommendation(x._1,x._2)) )
      }
      .toDF()
    userRecs.write
      .option("uri", mongoConfig.uri)
      .option("collection", USER_RECS)
      .mode("overwrite")
      .format("com.mongodb.spark.sql")
      .save()

    // 3. 利用商品的特征向量,计算商品的相似度列表
    val productFeatures = model.productFeatures.map{
    
    
      case (productId, features) => ( productId, new DoubleMatrix(features) )
    }
    // 两两配对商品,计算余弦相似度
    val productRecs = productFeatures.cartesian(productFeatures)
      .filter{
    
    
        case (a, b) => a._1 != b._1
      }
      // 计算余弦相似度
      .map{
    
    
        case (a, b) =>
          val simScore = consinSim( a._2, b._2 )
          ( a._1, ( b._1, simScore ) )
      }
      .filter(_._2._2 > 0.4)
      .groupByKey()
      .map{
    
    
        case (productId, recs) =>
          ProductRecs( productId, recs.toList.sortWith(_._2>_._2).map(x=>Recommendation(x._1,x._2)) )
      }
      .toDF()
    productRecs.write
      .option("uri", mongoConfig.uri)
      .option("collection", PRODUCT_RECS)
      .mode("overwrite")
      .format("com.mongodb.spark.sql")
      .save()

    spark.stop()
  }
  def consinSim(product1: DoubleMatrix, product2: DoubleMatrix): Double ={
    
    
    product1.dot(product2)/ ( product1.norm2() * product2.norm2() )
  }
}

第5章 リアルタイムレコメンドサービス構築【リアルタイムレコメンドモジュール】

  • レコメンデーション システムに適用した場合のリアルタイム計算とオフライン計算の最大の違いは、リアルタイム計算のレコメンデーション結果はユーザーの最近の好みを反映する必要があるのに対し、オフライン計算のレコメンデーション結果はユーザーのすべての好みに基づいて計算されることです。最初の評価から始まる評価レコード。全体的な好み。
  • アイテムに対するユーザーの好みは時間の経過とともに常に変化します。
    • たとえば、ユーザー u がある時点で製品 p に非常に高い評価を与えた場合、近い将来、u は製品 p に似た他の製品を好む可能性が非常に高くなります。
    • そして、ある瞬間にユーザー u が製品 q に非常に低い評価を付けた場合、近い将来、u は製品 q に似た他の製品を好まなくなる可能性が非常に高くなります。
    • したがって、リアルタイムの推奨の場合、ユーザーが製品を評価するとき、ユーザーは、推奨結果がユーザーの最近の好みと一致し、ユーザーの最近の好みを満たすように、推奨結果が最近の評価に基づいて更新されることを期待します。
  • リアルタイム レコメンデーションがオフライン レコメンデーションで ALS アルゴリズムを使用し続ける場合、アルゴリズムの実行時間が膨大になるため、新しいレコメンデーション結果をリアルタイムで取得する機能がなく、アルゴリズム自体が評価テーブルを使用するため、テーブル内の 1 つの項目は、アルゴリズム後の推奨結果を、ユーザーの現在の評価以前の推奨結果と基本的に同じにするため、ユーザーに推奨結果が変化していないように感じさせます。変更され、ユーザー エクスペリエンスに大きな影響を与えます。
  • また、リアルタイムレコメンドでは、時間性能がリアルタイムまたは準リアルタイムの要件を満たす必要があるため、複雑で過剰な計算によるユーザーエクスペリエンスの低下を避けるために、アルゴリズムの計算量が大きすぎてはなりません。このため、レコメンドの精度はそれほど高くないことが多いです。リアルタイムレコメンドシステムは、レコメンド結果の動的な変更能力をより重視しており、レコメンド結果を更新する理由が合理的である限り、レコメンドの精度要件を適切に緩和することができます。
  • したがって、リアルタイム推奨アルゴリズムには 2 つの主な要件があります。
    • システムは、ユーザーの現在の評価後、または最後の数回の評価後に推奨結果を更新できることは明らかです。
    • 計算量はそれほど多くなく、応答時間の点でリアルタイムまたは準リアルタイムの要件を満たします。

5.2 リアルタイムレコメンデーションモデルとコードフレームワーク

5.2.1 リアルタイムレコメンデーションモデルのアルゴリズム設計

5.2.2 リアルタイムレコメンドモジュールフレームワーク

  • recommender の下に新しいサブプロジェクト StreamingRecommender を作成し、spark、scala、mongo、redis、kafka への依存関係を導入します。
<dependencies>
    <!-- Spark的依赖引入 -->
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-core_2.11</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-sql_2.11</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-streaming_2.11</artifactId>
    </dependency>
    <!-- 引入Scala -->
    <dependency>
        <groupId>org.scala-lang</groupId>
        <artifactId>scala-library</artifactId>
    </dependency>

    <!-- 加入MongoDB的驱动 -->
    <!-- 用于代码方式连接MongoDB -->
    <dependency>
        <groupId>org.mongodb</groupId>
        <artifactId>casbah-core_2.11</artifactId>
        <version>${
    
    casbah.version}</version>
    </dependency>
    <!-- 用于Spark和MongoDB的对接 -->
    <dependency>
        <groupId>org.mongodb.spark</groupId>
        <artifactId>mongo-spark-connector_2.11</artifactId>
        <version>${
    
    mongodb-spark.version}</version>
    </dependency>

    <!-- redis -->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>

    <!-- kafka -->
    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
        <version>0.10.2.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
        <version>${
    
    spark.version}</version>
    </dependency>

</dependencies>
  • コードでは、まずサンプル クラスと接続ヘルパー オブジェクト (redis および mongo 接続の確立に使用) を定義し、StreamingRecommender でいくつかの定数を定義します。

コアコード: src/main/scala/com.atguigu.streaming/StreamingRecommender.scala

// 连接助手对象
object ConnHelper extends Serializable{
    
    
  lazy val jedis = new Jedis("localhost")
  lazy val mongoClient = MongoClient(MongoClientURI("mongodb://localhost:27017/recommender"))
}

case class MongConfig(uri:String,db:String)

// 标准推荐
case class Recommendation(productId:Int, score:Double)

// 用户的推荐
case class UserRecs(userId:Int, recs:Seq[Recommendation])

//商品的相似度
case class ProductRecs(productId:Int, recs:Seq[Recommendation])

object StreamingRecommender {
    
    

  val MAX_USER_RATINGS_NUM = 20
  val MAX_SIM_PRODUCTS_NUM = 20
  val MONGODB_STREAM_RECS_COLLECTION = "StreamRecs"
  val MONGODB_RATING_COLLECTION = "Rating"
  val MONGODB_PRODUCT_RECS_COLLECTION = "ProductRecs"
//入口方法
def main(args: Array[String]): Unit = {
    
    
	}
}
  • リアルタイムレコメンデーションのボディコードは次のとおりです。
def main(args: Array[String]): Unit = {
    
    

  val config = Map(
    "spark.cores" -> "local[*]",
    "mongo.uri" -> "mongodb://localhost:27017/recommender",
    "mongo.db" -> "recommender",
    "kafka.topic" -> "recommender"
  )
  //创建一个SparkConf配置
  val sparkConf = new SparkConf().setAppName("StreamingRecommender").setMaster(config("spark.cores"))
  val spark = SparkSession.builder().config(sparkConf).getOrCreate()
  val sc = spark.sparkContext
  val ssc = new StreamingContext(sc,Seconds(2))

  implicit val mongConfig = MongConfig(config("mongo.uri"),config("mongo.db"))
  import spark.implicits._

  // 广播商品相似度矩阵
  //装换成为 Map[Int, Map[Int,Double]]
  val simProductsMatrix = spark
    .read
    .option("uri",config("mongo.uri"))
    .option("collection",MONGODB_PRODUCT_RECS_COLLECTION)
    .format("com.mongodb.spark.sql")
    .load()
    .as[ProductRecs]   
    .rdd
    .map{
    
    recs =>
      (recs.productId,recs.recs.map(x=> (x.productId,x.score)).toMap)
    }.collectAsMap()  

  val simProductsMatrixBroadCast = sc.broadcast(simProductsMatrix)

  //创建到Kafka的连接
  val kafkaPara = Map(
    "bootstrap.servers" -> "localhost:9092",
    "key.deserializer" -> classOf[StringDeserializer],
    "value.deserializer" -> classOf[StringDeserializer],
    "group.id" -> "recommender",
    "auto.offset.reset" -> "latest"
  )

  val kafkaStream = KafkaUtils.createDirectStream[String,String](ssc,LocationStrategies.PreferConsistent,ConsumerStrategies.Subscribe[String,String](Array(config("kafka.topic")),kafkaPara))

  // UID|MID|SCORE|TIMESTAMP
  // 产生评分流
  val ratingStream = kafkaStream.map{
    
    case msg=>
    var attr = msg.value().split("\\|")
    (attr(0).toInt,attr(1).toInt,attr(2).toDouble,attr(3).toInt)
  }

// 核心实时推荐算法
  ratingStream.foreachRDD{
    
    rdd =>
    rdd.map{
    
    case (userId,productId,score,timestamp) =>
      println(">>>>>>>>>>>>>>>>")

      //获取当前最近的M次商品评分
      val userRecentlyRatings = getUserRecentlyRating(MAX_USER_RATINGS_NUM,userId,ConnHelper.jedis)

      //获取商品P最相似的K个商品
      val simProducts = getTopSimProducts(MAX_SIM_PRODUCTS_NUM,productId,userId,simProductsMatrixBroadCast.value)

      //计算待选商品的推荐优先级
      val streamRecs = computeProductScores(simProductsMatrixBroadCast.value,userRecentlyRatings,simProducts)

      //将数据保存到MongoDB
      saveRecsToMongoDB(userId,streamRecs)

    }.count()
  }

  //启动Streaming程序
  ssc.start()
  ssc.awaitTermination()
}

5.3 リアルタイムレコメンドアルゴリズムの実装

  • リアルタイムレコメンデーションアルゴリズムの前提:
    • Redis クラスターには、各ユーザーの製品に対する最近の K 評価が保存されます。リアルタイムアルゴリズムを迅速に取得できます。
    • オフライン推奨アルゴリズムは、MongoDB 内の製品類似度マトリックスを事前に計算しています。
    • Kafka はリアルタイムのユーザー評価データを取得しました。
  • アルゴリズムのプロセスは次のとおりです。
    • リアルタイム推奨アルゴリズムの入力は評価 <userId、productId、rate、timestamp> です。
    • 実装の中核となる要素は次のとおりです。
      • userId の最後の K 評価を取得する
      • productId に最も類似した K 製品を取得します
      • 候補製品の推奨優先度を計算
      • userId のリアルタイムの推奨結果を更新します

5.3.1 ユーザー K の最新評価を取得する

  • ビジネス サーバーがユーザー評価を受信すると、デフォルトでは userId、productId、rate、および timestamp の形式で、Redis のユーザーに対応するキューに評価が挿入されます。リアルタイム アルゴリズムでは、対応するキューの内容のみが挿入されます。できる
import scala.collection.JavaConversions._
/**
  * 获取当前最近的M次商品评分
  * @param num  评分的个数
  * @param userId  谁的评分
  * @return
  */
def getUserRecentlyRating(num:Int, userId:Int,jedis:Jedis): Array[(Int,Double)] ={
    
    
  //从用户的队列中取出num个评分
  jedis.lrange("userId:"+userId.toString, 0, num).map{
    
    item =>
    val attr = item.split("\\:")
    (attr(0).trim.toInt, attr(1).trim.toDouble)
  }.toArray
}

5.3.2 現在の製品に最も類似した K 個の製品を取得する

オフライン アルゴリズムでは、製品の類似性マトリックスが事前に計算されているため、各製品の productId で最も類似した K 製品を簡単に取得できます。MongoDB から ProductRecs データを読み取り、productId に対応するサブハッシュを取得します。 simHash テーブル内で、類似性が最も高い上位 K 個の商品を取得します。出力はデータ型 Array[Int] の配列で、productId に最も類似した製品コレクションを表し、候補製品コレクションとして CandidateProducts という名前が付けられます。

/**
  * 获取当前商品K个相似的商品
  * @param num          相似商品的数量
  * @param productId          当前商品的ID
  * @param userId          当前的评分用户
  * @param simProducts    商品相似度矩阵的广播变量值
  * @param mongConfig   MongoDB的配置
  * @return
  */
def getTopSimProducts(num:Int, productId:Int, userId:Int, simProducts:scala.collection.Map[Int,scala.collection.immutable.Map[Int,Double]])(implicit mongConfig: MongConfig): Array[Int] ={
    
    
  //从广播变量的商品相似度矩阵中获取当前商品所有的相似商品
  val allSimProducts = simProducts.get(productId).get.toArray
  //获取用户已经观看过得商品
  val ratingExist = ConnHelper.mongoClient(mongConfig.db)(MONGODB_RATING_COLLECTION).find(MongoDBObject("userId" -> userId)).toArray.map{
    
    item =>
    item.get("productId").toString.toInt
  }
  //过滤掉已经评分过得商品,并排序输出
  allSimProducts.filter(x => !ratingExist.contains(x._1)).sortWith(_._2 > _._2).take(num).map(x => x._1)
}

5.3.3 製品推奨優先度の計算

  • 候補製品セット simiHash および userId の最近の K 個の最近の Ratings の場合、アルゴリズム コードの内容は次のとおりです。
/**
  * 计算待选商品的推荐分数
  * @param simProducts            商品相似度矩阵
  * @param userRecentlyRatings  用户最近的k次评分
  * @param topSimProducts         当前商品最相似的K个商品
  * @return
  */
def computeProductScores(
	simProducts:scala.collection.Map[Int,scala.collection.immutable.Map[Int,Doub
	le]],userRecentlyRatings:Array[(Int,Double)],topSimProducts: Array[Int]): 
	Array[(Int,Double)] ={
    
    

  //用于保存每一个待选商品和最近评分的每一个商品的权重得分
  val score = scala.collection.mutable.ArrayBuffer[(Int,Double)]()

  //用于保存每一个商品的增强因子数
  val increMap = scala.collection.mutable.HashMap[Int,Int]()

  //用于保存每一个商品的减弱因子数
  val decreMap = scala.collection.mutable.HashMap[Int,Int]()

  for (topSimProduct <- topSimProducts; userRecentlyRating <- userRecentlyRatings){
    
    
    val simScore = getProductsSimScore(simProducts,userRecentlyRating._1,topSimProduct)
    if(simScore > 0.6){
    
    
      score += ((topSimProduct, simScore * userRecentlyRating._2 ))
      if(userRecentlyRating._2 > 3){
    
    
        increMap(topSimProduct) = increMap.getOrDefault(topSimProduct,0) + 1
      }else{
    
    
        decreMap(topSimProduct) = decreMap.getOrDefault(topSimProduct,0) + 1
      }
    }
  }

  score.groupBy(_._1).map{
    
    case (productId,sims) =>
    (productId,sims.map(_._2).sum / sims.length + log(increMap.getOrDefault(productId, 1)) - log(decreMap.getOrDefault(productId, 1)))
  }.toArray.sortWith(_._2>_._2)

}
  • このうちgetProductSimScoreは候補製品と評価対象製品の類似度を取得するもので、コードは以下の通りです。
/**
  * 获取当个商品之间的相似度
  * @param simProducts       商品相似度矩阵
  * @param userRatingProduct 用户已经评分的商品
  * @param topSimProduct     候选商品
  * @return
  */
def getProductsSimScore(
simProducts:scala.collection.Map[Int,scala.collection.immutable.Map[Int,Double]], userRatingProduct:Int, topSimProduct:Int): Double ={
    
    
  simProducts.get(topSimProduct) match {
    
    
    case Some(sim) => sim.get(userRatingProduct) match {
    
    
      case Some(score) => score
      case None => 0.0
    }
    case None => 0.0
  }
}
  • そして、log は対数演算であり、ここでは 10 の対数 (常用対数) を取得することで実装されています。
//取10的对数
def log(m:Int):Double ={
    
    
  math.log(m) / math.log(10)
}

5.3.4 結果をmongoDBに保存する

  • saveRecsToMongoDB 関数は、結果の保存を実装します。
/**
  * 将数据保存到MongoDB    userId -> 1,  recs -> 22:4.5|45:3.8
  * @param streamRecs  流式的推荐结果
  * @param mongConfig  MongoDB的配置
  */
def saveRecsToMongoDB(userId:Int,streamRecs:Array[(Int,Double)])(implicit mongConfig: MongConfig): Unit ={
    
    
  //到StreamRecs的连接
  val streaRecsCollection = ConnHelper.mongoClient(mongConfig.db)(MONGODB_STREAM_RECS_COLLECTION)

  streaRecsCollection.findAndRemove(MongoDBObject("userId" -> userId))
  streaRecsCollection.insert(MongoDBObject("userId" -> userId, "recs" ->
	streamRecs.map( x => MongoDBObject("productId"->x._1,"score"->x._2)) ))
}

5.3.5 リアルタイムの推奨結果を更新する

  • 候補製品の推奨優先度の updatedRecommends<productId, E> 配列を計算した後、この配列は Web バックグラウンド サーバーに送信され、マージされ、最新のリアルタイム推奨結果の userId の RecentRecommends<productId, E> と置き換えられます。バックグラウンド サーバー そして、この新しいリアルタイム推奨として、優先度 E を持つ上位 K 製品を選択します。特に:
    • Merge: updatedRecommends と RecentRecommends を新しい <productId, E> 配列にマージします。
    • 置換 (重複排除): updatedRecommends と RecentRecommends に重複する productId がある場合、recentRecommends の productId の推奨優先順位は最後のリアルタイム推奨の結果であるため、無効になり、updatedRecommends 優先順位の更新された productId を表す推奨に置き換えられます。
    • TopK を選択: マージおよび置換された <productId, E> 配列に基づいて、各製品の推奨優先度に従って、このリアルタイム推奨の最終結果として上位 K 製品を選択します。

コード本体:

package com.atguigu.online

import com.mongodb.casbah.commons.MongoDBObject
import com.mongodb.casbah.{
    
    MongoClient, MongoClientURI}
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession
import org.apache.spark.streaming.kafka010.{
    
    ConsumerStrategies, KafkaUtils, LocationStrategies}
import org.apache.spark.streaming.{
    
    Seconds, StreamingContext}
import redis.clients.jedis.Jedis

// 定义一个连接助手对象,建立到redis和mongodb的连接
object ConnHelper extends Serializable{
    
    
  // 懒变量定义,使用的时候才初始化
  lazy val jedis = new Jedis("localhost")
  lazy val mongoClient = MongoClient(MongoClientURI("mongodb://localhost:27017/recommender"))
}

case class MongoConfig( uri: String, db: String )

// 定义标准推荐对象
case class Recommendation( productId: Int, score: Double )
// 定义用户的推荐列表
case class UserRecs( userId: Int, recs: Seq[Recommendation] )
// 定义商品相似度列表
case class ProductRecs( productId: Int, recs: Seq[Recommendation] )

object OnlineRecommender {
    
    
  // 定义常量和表名
  val MONGODB_RATING_COLLECTION = "Rating"
  val STREAM_RECS = "StreamRecs"
  val PRODUCT_RECS = "ProductRecs"

  val MAX_USER_RATING_NUM = 20
  val MAX_SIM_PRODUCTS_NUM = 20

  def main(args: Array[String]): Unit = {
    
    
    val config = Map(
      "spark.cores" -> "local[*]",
      "mongo.uri" -> "mongodb://localhost:27017/recommender",
      "mongo.db" -> "recommender",
      "kafka.topic" -> "recommender"
    )

    // 创建spark conf
    val sparkConf = new SparkConf().setMaster(config("spark.cores")).setAppName("OnlineRecommender")
    val spark = SparkSession.builder().config(sparkConf).getOrCreate()
    val sc = spark.sparkContext
    val ssc = new StreamingContext(sc, Seconds(2))

    import spark.implicits._
    implicit val mongoConfig = MongoConfig( config("mongo.uri"), config("mongo.db") )

    // 加载数据,相似度矩阵,广播出去
    val simProductsMatrix = spark.read
      .option("uri", mongoConfig.uri)
      .option("collection", PRODUCT_RECS)
      .format("com.mongodb.spark.sql")
      .load()
      .as[ProductRecs]
      .rdd
      // 为了后续查询相似度方便,把数据转换成map形式
      .map{
    
    item =>
        ( item.productId, item.recs.map( x=>(x.productId, x.score) ).toMap )
      }
      .collectAsMap()
    // 定义广播变量
    val simProcutsMatrixBC = sc.broadcast(simProductsMatrix)

    // 创建kafka配置参数
    val kafkaParam = Map(
      "bootstrap.servers" -> "localhost:9092",
      "key.deserializer" -> classOf[StringDeserializer],
      "value.deserializer" -> classOf[StringDeserializer],
      "group.id" -> "recommender",
      "auto.offset.reset" -> "latest"
    )
    // 创建一个DStream
    val kafkaStream = KafkaUtils.createDirectStream[String, String]( ssc,
      LocationStrategies.PreferConsistent,
      ConsumerStrategies.Subscribe[String, String]( Array(config("kafka.topic")), kafkaParam )
    )
    // 对kafkaStream进行处理,产生评分流,userId|productId|score|timestamp
    val ratingStream = kafkaStream.map{
    
    msg=>
      var attr = msg.value().split("\\|")
      ( attr(0).toInt, attr(1).toInt, attr(2).toDouble, attr(3).toInt )
    }

    // 核心算法部分,定义评分流的处理流程
    ratingStream.foreachRDD{
    
    
      rdds => rdds.foreach{
    
    
        case ( userId, productId, score, timestamp ) =>
          println("rating data coming!>>>>>>>>>>>>>>>>>>")

          // TODO: 核心算法流程
          // 1. 从redis里取出当前用户的最近评分,保存成一个数组Array[(productId, score)]
          val userRecentlyRatings = getUserRecentlyRatings( MAX_USER_RATING_NUM, userId, ConnHelper.jedis )

          // 2. 从相似度矩阵中获取当前商品最相似的商品列表,作为备选列表,保存成一个数组Array[productId]
          val candidateProducts = getTopSimProducts( MAX_SIM_PRODUCTS_NUM, productId, userId, simProcutsMatrixBC.value )

          // 3. 计算每个备选商品的推荐优先级,得到当前用户的实时推荐列表,保存成 Array[(productId, score)]
          val streamRecs = computeProductScore( candidateProducts, userRecentlyRatings, simProcutsMatrixBC.value )

          // 4. 把推荐列表保存到mongodb
          saveDataToMongoDB( userId, streamRecs )
      }
    }

    // 启动streaming
    ssc.start()
    println("streaming started!")
    ssc.awaitTermination()

  }

  /**
    * 从redis里获取最近num次评分
    */
  import scala.collection.JavaConversions._
  def getUserRecentlyRatings(num: Int, userId: Int, jedis: Jedis): Array[(Int, Double)] = {
    
    
    // 从redis中用户的评分队列里获取评分数据,list键名为uid:USERID,值格式是 PRODUCTID:SCORE
    jedis.lrange( "userId:" + userId.toString, 0, num )
      .map{
    
     item =>
        val attr = item.split("\\:")
        ( attr(0).trim.toInt, attr(1).trim.toDouble )
      }
      .toArray
  }
  // 获取当前商品的相似列表,并过滤掉用户已经评分过的,作为备选列表
  def getTopSimProducts(num: Int,
                        productId: Int,
                        userId: Int,
                        simProducts: scala.collection.Map[Int, scala.collection.immutable.Map[Int, Double]])
                       (implicit mongoConfig: MongoConfig): Array[Int] ={
    
    
    // 从广播变量相似度矩阵中拿到当前商品的相似度列表
    val allSimProducts = simProducts(productId).toArray

    // 获得用户已经评分过的商品,过滤掉,排序输出
    val ratingCollection = ConnHelper.mongoClient( mongoConfig.db )( MONGODB_RATING_COLLECTION )
    val ratingExist = ratingCollection.find( MongoDBObject("userId"->userId) )
      .toArray
      .map{
    
    item=> // 只需要productId
        item.get("productId").toString.toInt
      }
    // 从所有的相似商品中进行过滤
    allSimProducts.filter( x => ! ratingExist.contains(x._1) )
      .sortWith(_._2 > _._2)
      .take(num)
      .map(x=>x._1)
  }
  // 计算每个备选商品的推荐得分
  def computeProductScore(candidateProducts: Array[Int],
                          userRecentlyRatings: Array[(Int, Double)],
                          simProducts: scala.collection.Map[Int, scala.collection.immutable.Map[Int, Double]])
  : Array[(Int, Double)] ={
    
    
    // 定义一个长度可变数组ArrayBuffer,用于保存每一个备选商品的基础得分,(productId, score)
    val scores = scala.collection.mutable.ArrayBuffer[(Int, Double)]()
    // 定义两个map,用于保存每个商品的高分和低分的计数器,productId -> count
    val increMap = scala.collection.mutable.HashMap[Int, Int]()
    val decreMap = scala.collection.mutable.HashMap[Int, Int]()

    // 遍历每个备选商品,计算和已评分商品的相似度
    for( candidateProduct <- candidateProducts; userRecentlyRating <- userRecentlyRatings ){
    
    
      // 从相似度矩阵中获取当前备选商品和当前已评分商品间的相似度
      val simScore = getProductsSimScore( candidateProduct, userRecentlyRating._1, simProducts )
      if( simScore > 0.4 ){
    
    
        // 按照公式进行加权计算,得到基础评分
        scores += ( (candidateProduct, simScore * userRecentlyRating._2) )
        if( userRecentlyRating._2 > 3 ){
    
    
          increMap(candidateProduct) = increMap.getOrDefault(candidateProduct, 0) + 1
        } else {
    
    
          decreMap(candidateProduct) = decreMap.getOrDefault(candidateProduct, 0) + 1
        }
      }
    }

    // 根据公式计算所有的推荐优先级,首先以productId做groupby
    scores.groupBy(_._1).map{
    
    
      case (productId, scoreList) =>
        ( productId, scoreList.map(_._2).sum/scoreList.length + log(increMap.getOrDefault(productId, 1)) - log(decreMap.getOrDefault(productId, 1)) )
    }
    // 返回推荐列表,按照得分排序
      .toArray
      .sortWith(_._2>_._2)
  }

  def getProductsSimScore(product1: Int, product2: Int,
                          simProducts: scala.collection.Map[Int, scala.collection.immutable.Map[Int, Double]]): Double ={
    
    
    simProducts.get(product1) match {
    
    
      case Some(sims) => sims.get(product2) match {
    
    
        case Some(score) => score
        case None => 0.0
      }
      case None => 0.0
    }
  }
  // 自定义log函数,以N为底
  def log(m: Int): Double = {
    
    
    val N = 10
    math.log(m)/math.log(N)
  }
  // 写入mongodb
  def saveDataToMongoDB(userId: Int, streamRecs: Array[(Int, Double)])(implicit mongoConfig: MongoConfig): Unit ={
    
    
    val streamRecsCollection = ConnHelper.mongoClient(mongoConfig.db)(STREAM_RECS)
    // 按照userId查询并更新
    streamRecsCollection.findAndRemove( MongoDBObject( "userId" -> userId ) )
    streamRecsCollection.insert( MongoDBObject( "userId" -> userId,
                                  "recs" -> streamRecs.map(x=>MongoDBObject("productId"->x._1, "score"->x._2)) ) )
  }

}

5.4 リアルタイムシステム共同デバッグ

  • 当社のシステムがリアルタイムで推奨するデータ フローの方向は、ビジネス システム -> ログ -> Flume ログ収集 -> Kafka ストリーミング データのクリーニングと前処理 -> Spark ストリーミング ストリーミング コンピューティングです。リアルタイム レコメンデーション サービスのコードが完成したら、他のツールと共同デバッグ テストを実施して、システムが正常に動作していることを確認する必要があります。

5.4.1 リアルタイム システムの基本コンポーネントの起動

  • リアルタイムレコメンデーションシステム StreamingRecommender および mongodb と redis を開始します

5.4.2 Zookeeper の開始

bin/zkServer.sh 起動

5.4.3 カフカの起動

bin/kafka-server-start.sh -daemon ./config/server.properties

5.4.4 Kafka ストリーミング プログラムのビルド

  • レコメンダーの下に新しいモジュール KafkaStreaming を作成します。これは主にログ データを前処理し、必要なコンテンツをフィルタリングするために使用されます。pom.xml ファイルには依存関係を導入する必要があります。
<dependencies>
    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-streams</artifactId>
        <version>0.10.2.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
        <version>0.10.2.1</version>
    </dependency>
</dependencies>

<build>
    <finalName>kafkastream</finalName>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <archive>
                    <manifest>
                        <mainClass>com.atguigu.kafkastream.Application</mainClass>
                    </manifest>
                </archive>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
  • 新しい Java クラス com.atguigu.kafkastreaming.Application を src/main/java の下に作成します。
public class Application {
    
    
    public static void main(String[] args){
    
    

        String brokers = "localhost:9092";
        String zookeepers = "localhost:2181";

        // 定义输入和输出的topic
        String from = "log";
        String to = "recommender";

        // 定义kafka streaming的配置
        Properties settings = new Properties();
        settings.put(StreamsConfig.APPLICATION_ID_CONFIG, "logFilter");
        settings.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, brokers);
        settings.put(StreamsConfig.ZOOKEEPER_CONNECT_CONFIG, zookeepers);

        StreamsConfig config = new StreamsConfig(settings);

        // 拓扑建构器
        TopologyBuilder builder = new TopologyBuilder();

        // 定义流处理的拓扑结构
        builder.addSource("SOURCE", from)
                .addProcessor("PROCESS", () -> new LogProcessor(), "SOURCE")
                .addSink("SINK", to, "PROCESS");

        KafkaStreams streams = new KafkaStreams(builder, config);
        streams.start();
    }
}
  • このプログラムは、トピック「ログ」を持つ情報フローを取得して処理し、それを新しいトピックとして「レコメンダー」として転送します。
  • ストリーム処理プログラムLogProcess.java
public class LogProcessor implements Processor<byte[],byte[]> {
    
    
    private ProcessorContext context;

    public void init(ProcessorContext context) {
    
    
        this.context = context;
    }

    public void process(byte[] dummy, byte[] line) {
    
    
        String input = new String(line);
        // 根据前缀过滤日志信息,提取后面的内容
        if(input.contains("PRODUCT_RATING_PREFIX:")){
    
    
            System.out.println("product rating coming!!!!" + input);
            input = input.split("PRODUCT_RATING_PREFIX:")[1].trim();
            context.forward("logProcessor".getBytes(), input.getBytes());
        }
    }
    public void punctuate(long timestamp) {
    
    
    }
    public void close() {
    
    
    }
}
  • コードを完成させたら、アプリケーションを起動します。

5.4.5 Flumeの設定と開始

  • flume の conf ディレクトリに新しい log-kafka.properties を作成し、kafka に接続するように flume を構成します。
agent.sources = exectail
agent.channels = memoryChannel
agent.sinks = kafkasink

# For each one of the sources, the type is defined
agent.sources.exectail.type = exec
# 下面这个路径是需要收集日志的绝对路径,改为自己的日志目录
agent.sources.exectail.command = tail –f
/mnt/d/Projects/BigData/ECommerceRecommenderSystem/businessServer/src/main/log/agent.log
agent.sources.exectail.interceptors=i1
agent.sources.exectail.interceptors.i1.type=regex_filter
# 定义日志过滤前缀的正则
agent.sources.exectail.interceptors.i1.regex=.+PRODUCT_RATING_PREFIX.+
# The channel can be defined as follows.
agent.sources.exectail.channels = memoryChannel

# Each sink's type must be defined
agent.sinks.kafkasink.type = org.apache.flume.sink.kafka.KafkaSink
agent.sinks.kafkasink.kafka.topic = log
agent.sinks.kafkasink.kafka.bootstrap.servers = localhost:9092
agent.sinks.kafkasink.kafka.producer.acks = 1
agent.sinks.kafkasink.kafka.flumeBatchSize = 20

#Specify the channel the sink should use
agent.sinks.kafkasink.channel = memoryChannel

# Each channel's type is defined.
agent.channels.memoryChannel.type = memory

# Other config values specific to each type of channel(sink or source)
# can be defined as well
# In this case, it specifies the capacity of the memory channel
agent.channels.memoryChannel.capacity = 10000

設定後、flume を起動します:
./bin/flume-ng Agent -c ./conf/ -f ./conf/log-kafka.properties -n Agent -Dflume.root.logger=INFO,console

5.4.6 業務システムバックグラウンド起動

  • ビジネスコードをシステムに追加します。src/main/resources/ の log4j.properties で、log4j.appender.file.File の値を独自のログ ディレクトリに置き換える必要があることに注意してください。これは、flume の設定と同じである必要があります。
  • ビジネス システム バックエンドを起動し、localhost:8088/index.html にアクセスし、評価する製品をクリックして、リアルタイムの推奨リストが変更されるかどうかを確認します。

第 6 章 コールド スタートの問題の処理

  • レコメンド システム全体が、製品をレコメンドするために使用される嗜好情報に依存しているため、問題が発生します。新規登録ユーザーの場合、嗜好情報の記録が存在しません。そうすると、この時点でのレコメンドに問題が発生し、結果として「Any」が表示されなくなります。おすすめアイテムが登場します。
  • この問題に対処するには、ユーザーが初めてログインするときに、アイテムに対するユーザーの好みを取得するためのインタラクティブなウィンドウをユーザーに提供し、ユーザーがあらかじめ設定されている興味タグを確認できるようにします。
    ユーザーの好みを取得した後、対応するタイプの製品を直接推奨することができます。

第7章 他の形態のオフライン類似レコメンドサービス

7.1 コンテンツベースの同様の推奨事項

  • 元データのタグファイルはユーザーが商品に付けたタグであり、この部分のコンテンツを直接スコアに変換することは容易ではありませんが、タグの内容を抽出して商品のコンテンツ特徴ベクトルを取得することは可能です。そして類似度行列を取得します。この部分は、リアルタイム推奨システムに直接接続して、ユーザーが現在評価している製品に類似した製品を計算し、コンテンツベースのリアルタイム推奨を実現できます。特徴抽出に対する人気のあるタグの影響を回避するために、TF-IDF アルゴリズムを通じてタグの重みを調整して、ユーザーの好みにできる限り近づけることもできます。
  • 上記の考えに基づいて、TF-IDF アルゴリズムを追加して製品特徴ベクトルを取得するためのコア コードは次のとおりです。
// 载入商品数据集
val productTagsDF = spark
  .read
  .option("uri",mongoConfig.uri)
  .option("collection",MONGODB_PRODUCT_COLLECTION)
  .format("com.mongodb.spark.sql")
  .load()
  .as[Product]
  .map(x => (x.productId, x.name, x.genres.map(c => if(c == '|') ' ' else c)))
  .toDF("productId", "name", "tags").cache()

// 实例化一个分词器,默认按空格分
val tokenizer = new Tokenizer().setInputCol("tags").setOutputCol("words")

// 用分词器做转换
val wordsData = tokenizer.transform(productTagsDF)

// 定义一个HashingTF工具
val hashingTF = new HashingTF().setInputCol("words").setOutputCol("rawFeatures").setNumFeatures(200)

// 用 HashingTF 做处理
val featurizedData = hashingTF.transform(wordsData)

// 定义一个IDF工具
val idf = new IDF().setInputCol("rawFeatures").setOutputCol("features")

// 将词频数据传入,得到idf模型(统计文档)
val idfModel = idf.fit(featurizedData)

// 用tf-idf算法得到新的特征矩阵
val rescaledData = idfModel.transform(featurizedData)

// 从计算得到的 rescaledData 中提取特征向量
val productFeatures = rescaledData.map{
    
    
  case row => ( row.getAs[Int]("productId"),row.getAs[SparseVector]("features").toArray )
}
  .rdd
  .map(x => {
    
    
    (x._1, new DoubleMatrix(x._2) )
  })
  • 次に、製品の特徴ベクトルを通じて類似性行列を取得し、製品の詳細ページ (通常は電子商取引 Web サイトで、ユーザーが製品を閲覧するか購入を完了した後) で同様の推奨リストを提供できます。と表示されます。
  • 取得された類似性行列は、リアルタイムの推奨の基礎を提供し、ユーザーの推奨リストを取得することもできます。コンテンツベースの潜在意味論的モデルの目的は、類似性行列を計算できるようにアイテムの特徴ベクトルを抽出することであることがわかります。当社のリアルタイム レコメンデーション システム アルゴリズムは類似性に基づいて定義されています。

7.2 アイテムベースの協調フィルタリングの類似レコメンデーション

  • アイテムベースの協調フィルタリング(Item-CF)は、ユーザーの日常的な行動データ(クリック、お気に入り、購入など)を収集するだけで、製品間の類似性を取得でき、実際のプロジェクトで広く使用されています。
  • 私たちの全体的な考え方は、2 つの製品が同じ視聴者 (関心のある人々) を持っている場合、それらは本質的に関連しているということです。したがって、既存の行動データを使用して製品視聴者の類似性を分析し、製品間の類似性を導き出すことができます。この方法を項目の「共起類似度」と定義し、計算式は次のようになります。
    ここに画像の説明を挿入します
  • このうち、Ni は製品 i を購入した (または製品 i を評価した) ユーザのリストであり、Nj は製品 j を購入したユーザのリストです。
  • コアコードは次のように実装されます。
 val ratingDF = spark.read
      .option("uri", mongoConfig.uri)
      .option("collection", MONGODB_RATING_COLLECTION)
      .format("com.mongodb.spark.sql")
      .load()
      .as[Rating]
      .map(x=> (x.userId, x.productId, x.score) )
      .toDF("userId", "productId", "rating")

    // 统计每个商品的评分个数,并通过内连接添加到 ratingDF 中
    val numRatersPerProduct = ratingDF.groupBy("productId").count()
    val ratingWithCountDF = ratingDF.join(numRatersPerProduct, "productId")

    // 将商品评分按 userId 两两配对,可以统计两个商品被同一用户做出评分的次数
    val joinedDF = ratingWithCountDF.join(ratingWithCountDF, "userId")
      .toDF("userId", "product1", "rating1", "count1", "product2", "rating2", "count2")
      .select("userId", "product1", "count1", "product2", "count2")
    joinedDF.createOrReplaceTempView("joined")
    val cooccurrenceDF = spark.sql(
      """
        |select product1
        |, product2
        |, count(userId) as coocount
        |, first(count1) as count1
        |, first(count2) as count2
        |from joined
        |group by product1, product2
      """.stripMargin
    ).cache()

    val simDF = cooccurrenceDF.map{
    
     row =>
      // 用同现的次数和各自的次数,计算同现相似度
      val coocSim = cooccurrenceSim( row.getAs[Long]("coocount"), row.getAs[Long]("count1"), row.getAs[Long]("count2") )
      ( row.getAs[Int]("product1"), ( row.getAs[Int]("product2"), coocSim ) )
    }
      .rdd
      .groupByKey()
      .map{
    
    
        case (productId, recs) =>
          ProductRecs( productId,
            recs.toList
              .filter(x=>x._1 != productId)
              .sortWith(_._2>_._2)
              .map(x=>Recommendation(x._1,x._2))
              .take(MAX_RECOMMENDATION)
          )
      }
      .toDF()
  • このうち、共起類似度を計算する関数コードは以下のように実装されています。
def cooccurrenceSim(cooCount: Long, count1: Long, count2: Long): Double ={
    
    
      cooCount / math.sqrt( count1 * count2 )
    }

第 8 章 プログラムの展開と運用

8.1 プロジェクトの公開

  • プロジェクトをコンパイルします。ルート プロジェクトのクリーン パッケージ フェーズを実行します。
    ここに画像の説明を挿入します

8.2 フロントエンドプロジェクトのインストール

  • 次のように、website-release.tar.gz を /var/www/html ディレクトリに解凍し、その中のファイルをルート ディレクトリに配置します。
    ここに画像の説明を挿入します
  • Apache サーバーを起動し、http://IP:80 にアクセスします。

8.3 業務サーバーのインストール

  • BusinessServer.war を Tomcat の webapp ディレクトリに配置し、抽出したファイルを ROOT ディレクトリに配置します。
    ここに画像の説明を挿入します
  • Tomcatサーバーを起動します

8.4 Kafkaの構成と起動

  • カフカを開始する
  • kafka で 2 つのトピックを作成します。1 つはログ用、もう 1 つはレコメンダー用です。
  • kafkaStream プログラムを開始して、ログとレコメンダー トピック間のデータをフォーマットします。
java -cp kafkastream.jar com.atguigu.kafkastream.Application linux:9092 linux:2181 log recommender

8.5 Flumeの設定と起動

  • Flume インストールディレクトリの conf フォルダーに log-kafka.properties を作成します。
agent.sources = exectail
agent.channels = memoryChannel
agent.sinks = kafkasink

# For each one of the sources, the type is defined
agent.sources.exectail.type = exec
agent.sources.exectail.command = tail -f /home/bigdata/cluster/apache-tomcat-8.5.23/logs/catalina.out
agent.sources.exectail.interceptors=i1
agent.sources.exectail.interceptors.i1.type=regex_filter
agent.sources.exectail.interceptors.i1.regex=.+PRODUCT_RATING_PREFIX.+
# The channel can be defined as follows.
agent.sources.exectail.channels = memoryChannel

# Each sink's type must be defined
agent.sinks.kafkasink.type = org.apache.flume.sink.kafka.KafkaSink
agent.sinks.kafkasink.kafka.topic = log
agent.sinks.kafkasink.kafka.bootstrap.servers = linux:9092
agent.sinks.kafkasink.kafka.producer.acks = 1
agent.sinks.kafkasink.kafka.flumeBatchSize = 20


#Specify the channel the sink should use
agent.sinks.kafkasink.channel = memoryChannel

# Each channel's type is defined.
agent.channels.memoryChannel.type = memory

# Other config values specific to each type of channel(sink or source)
# can be defined as well
# In this case, it specifies the capacity of the memory channel
agent.channels.memoryChannel.capacity = 10000
  • 水路を開始します
bin/flume-ng agent -c ./conf/ -f ./conf/log-kafka.properties -n agent

8.6 ストリーミング コンピューティング サービスの展開

  • SparkStreaming プログラムを送信します。
bin/spark-submit --class com.atguigu.streamingRecommender.StreamingRecommender streamingRecommender-1.0-SNAPSHOT.jar

8.7 アズカバンのオフライン スケジューリング アルゴリズム

  • スケジュールプロジェクトの作成
    ここに画像の説明を挿入します
  • 次のように 2 つのジョブ ファイルを作成します。
  • アズカバン統計の仕事:
type=command
command=/home/bigdata/cluster/spark-2.1.1-bin-hadoop2.7/bin/spark-submit --class com.atguigu.offline.RecommenderTrainerApp
 offlineRecommender-1.0-SNAPSHOT.jar
  • アズカバンオフライン.ジョブ:
type=command
command=/home/bigdata/cluster/spark-2.1.1-bin-hadoop2.7/bin/spark-submit --class com.atguigu.statisticsRecommender.StatisticsApp
 statisticsRecommender-1.0-SNAPSHOT.jar
  • ジョブ ファイルを ZIP パッケージとして azkaban にアップロードします。
    ここに画像の説明を挿入します
  • 次のように:
    ここに画像の説明を挿入します
  • 各タスクの指定時間を個別に定義します。
    ここに画像の説明を挿入します
  • 定義が完了したら、「スケジューラ」をクリックします。

おすすめ

転載: blog.csdn.net/Lenhart001/article/details/131566357