Spark Structured Streaming 入门学习:集成Kafka实时流处理

975 阅读1分钟

场景

希望对应用产生的某类型的日志数据,进行实时分析。日志数据以文件形式保存在服务器磁盘中,每一行为一个事件:{"time": 1469501675,"action": "Open"}JSON形式。

方案

使用Filebeat转发数据到Kafka,将Kafka作为输入数据流,由Spark Streaming进行计算。 Filebeat是轻量级的代理,非常简单易用,支持多种安装方式。 Kafka是一个消息队列,也是一个流处理平台。这里作为一个消息队列(负责分发和储存)。 Spark Streaming负责处理流数据

实践

资源准备

  • Filebeat 6.3.2
  • Kafka 0.10
  • Spark 2.4
  • Scala 2.11
  • CentOS 7.4 64位

安装&配置Kafka

启动Zookeeper Server:nohup bin/zookeeper-server-start.sh config/zookeeper.properties &

启动Kafka Server:nohup bin/kafka-server-start.sh config/server.properties &

创建Topic: bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic log-topic

安装&配置Filebeat

版本6.3.2 下载地址 直接多种安装方式,这里使用命令行启动。 修改配置文件:(/root/filebeat_kafka_spark/filebeat-6.3.2-linux-x86_64/filebeat.yml)

filebeat.inputs:
- type: log
  enabled: true
  paths:
    - /root/logs/*.log

#============================= Filebeat modules ===============================

filebeat.config.modules:
  path: ${path.config}/modules.d/*.yml
  reload.enabled: false

name: "log-shipper"
fields:
  log_topic: "log-topic"


#================================ Outputs =====================================
output.kafka:
  hosts: ["localhost:9092"]
  topic: '%{[fields.log_topic]}'
  partition.round_robin:
    reachable_only: false

  required_acks: 1
  compression: gzip
  max_message_bytes: 1000000

启动Filebeat,开始日志收集、转发。 ./filebeat -e -c filebeat.yml -d "publish"

日志内容

从Filebeat中转发到Kafka日志内容会增加一些信息:

{
  "@timestamp": "2018-12-14T03:16:14.600Z",
  "@metadata": {
    "beat": "filebeat",
    "type": "doc",
    "version": "6.3.2"
  },
  "prospector": {
    "type": "log"
  },
  "input": {
    "type": "log"
  },
  "fields": {
    "log_topic": "log-topic"
  },
  "beat": {
    "name": "log-shipper",
    "hostname": "ecs-f3a2.novalocal",
    "version": "6.3.2"
  },
  "host": {
    "name": "log-shipper"
  },
  "source": "/root/logs/access.log",
  "offset": 261,
  "message": "{\"time\":1469501675,\"action\":\"Open\"}"
}

每一行日志内容为message 字段。

Spark 代码

import java.sql.Timestamp

import com.google.gson.{JsonObject, JsonParser}
import org.apache.spark.sql._

case class Event(action: String, time: java.sql.Timestamp)

object App {

  def main(args: Array[String]): Unit = {


    val spark = SparkSession.builder()
      .master("local[2]")
      .appName("log-streaming-app")
      .getOrCreate()
    import spark.implicits._

    // 从 Kafka 读取流
    val df = spark
      .readStream
      .format("kafka")
      .option("kafka.bootstrap.servers", "114.115.241.195:9092")
      .option("subscribe", "nginx-log-topic")
      .load()


    df.printSchema()
    /*
        root
        |-- key: binary (nullable = true)
        |-- value: binary (nullable = true)   // 从Kafka读取的数据为这个字段
        |-- topic: string (nullable = true)
        |-- partition: integer (nullable = true)
        |-- offset: long (nullable = true)
        |-- timestamp: timestamp (nullable = true)
        |-- timestampType: integer (nullable = true)
    */

    // 接下来对 df 进行操作
    val value = df.selectExpr("CAST(value AS STRING)").as[String]

    //  把value中的message解析出来,message为原始数据
    val data = value.map((v: String) => {
      val json = new JsonParser()
      val obj = json.parse(v).asInstanceOf[JsonObject]
      val message = obj.get("message").getAsString
      val jsonData = json.parse(message).getAsJsonObject
      var action = jsonData.get("action").getAsString
      val time = jsonData.get("time").getAsLong
      Event(action, new Timestamp(time * 1000))
    })


    // 按 "action" 分组 累计统计
    val result = data.groupBy($"action").count()


    // 结果 输出
    result.writeStream
      .foreach(new ForeachWriter[Row] {
        override def open(partitionId: Long, epochId: Long): Boolean = {
          true
        }

        override def process(value: Row): Unit = {
          println(value.getString(0), value.getLong(1))
        }

        override def close(errorOrNull: Throwable): Unit = {

        }
      })
      .outputMode("complete")
      .start().awaitTermination()

  }

}

调试运行,结果正确后,打包,在Spark集群中运行。

总结

入门学习 Spark Structured Streaming,里面要掌握的知识还是很多的,包括对 Scala、Kafka、Spark DataFrame/Dataset 操作等。

参考资料