最近开搞spark streaming,记录下一个apache log analysis demo的部署过程。

开发环境是Mac os + scala 2.11 + spark 2.0 + kafka 0.10 + Intellij Idea。


安装 scala(如果已经安装完毕就跳过)

        Mac os系统下使用 brew安装  ,为确保版本问题, 先运行       

$ brew update


           然后运行:       

$ brew install scala



     

安装 Intellij Idea:

       此次使用 Intellij Idea作为ide来开发, 不过eclipse也行,但是没有尝试,所以eclipse不做记录。

       Intell Idea可以在其官网下载到,社区版和付费版貌似都可以用来开发scala程序,不过有edu邮箱的可以有一年免费使用付费版的机会,不要浪费。

       安装时不要忘记顺便安装scala的插件,不过装好后也能安装插件, 略去不表。

       接下来就是新建scala project, 当然也可以建sbt project,方式为 file -> new -> project ->scala, 确保安装的scala版本为2.11即可。


安装spark 2.0:

      Mac 同样使用 brew安装:

$ brew install apache-spark


      等到安装结束, 运行以下命令:

$ spark-shell


       如果看到

Welcome to
      ____              __
     / __/__  ___ _____/ /__
    _\ \/ _ \/ _ `/ __/  '_/
   /___/ .__/\_,_/_/ /_/\_\   version 2.0.0
      /_/
         
   Using Scala version 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_60)
   Type in expressions to have them evaluated.
   Type :help for more information.



     

就说明成功了。 但是在shell里面出现的信息太多, 找个error都不太容易。为了让输出好看点, 要改下 log4j.properties.template 配置文件, 其路径为:  


             /usr/local/Cellar/apache-spark/2.0.0/libexec/conf

      找到其中的 log4j.rootCategory,把 INFO 改成ERROR ,以后就只有ERROR会被输出来了。 spark到此算是可以运行了。

      (明天继续,去弄Deep Learning了~~~~~~)


安装 Kafka:

      为了安装Kafka,必须先要安装zookeeper(如果已经安装,这步可以略去)。Mac 系统下用brew来进行安装:

$ brew install zookeeper



     等下就安装完毕了,这里我们使用默认的配置文件来启动服务,所以直接运行:


$ zkserver start

    稍等片刻,zookeeper就启动了,下面就可以继续安装kafka了。


    安装配置kafka的过程可以参考官方文档,这里给出链接:http://kafka.apache.org/documentation.html#quickstart

    在Mac os下安装稍有不同,这里给出具体过程:

    首先, 通过brew安装kafka:

$ brew install kafka

    等待其安装完成,去到kafka的相应目录:


$ cd /usr/local/Cellar/kafka/0.10.0.1/libexec

    然后执行命令:


$ kafka-server-start config/server.properties

    然后服务就启动啦。下面我们来创建一个topic。同样在上面那个路径下, 执行:


$ kafka-topics --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test



    我们就创建了一个叫test的topic, 我们可以list出所有创建的topics,来查看刚才创建的那个topic是否存在:


$ kafka-topics --list --zookeeper localhost:2181



    就可以看到一个叫test的topic了。现在我们使用producer来生产点数据看看:


$ kafka-console-producer --broker-list localhost:9092 --topic test

    然后尝试输入消息:


hello world!
  hello again

     然后可以新开一个终端, 回到刚才的目录, 或者直接control + c退出。然后使用consumer 来尝试接受数据:


$ kafka-console-consumer --zookeeper localhost:2181 --topic test --from-beginning



     应该就能看到刚才产生的那两条消息了,说明单个节点配置成功了。多节点的配置这里就略去不表。



Spark streaming开发环境测试:

Intellij中创建的scala project中,首先要把需要要到的库安装下。首先把spark的jar文件导入进来:

    点击file -> project struct, 然后在打开的窗口中选择Libraries,点击“+”这个符号, 然后选择java,把spark的jar文件全部导入。

    这些jar文件的具体位置在为 /usr/local/Cellar/apache-spark/2.0.0/libexec/jars

    

    同样导入kafak的jar文件, 不过具体位置在 /usr/local/Cellar/kafka/0.10.0.1/libexec/libs。


    现在要导入spark streaming,和上面差不多, 不过在选择时不选java, 选from maven,对应次开发环境的包是  spark-streaming-kafka-0-10_2.11


导入包的工作完成后,在project的src文件夹下创建两个scala源文件:

    

   

KafkaExample.scala:


import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.storage.StorageLevel

import java.util.regex.Pattern
import java.util.regex.Matcher

import com.sundogsoftware.sparkstreaming.Utilities._

import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.streaming.kafka010._
import org.apache.spark.streaming.kafka010.LocationStrategies.PreferConsistent
import org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe

/** Working example of listening for log data from Kafka's testLogs topic on port 9092. */
object KafkaExample {
  
  def main(args: Array[String]) {

    // Create the context with a 1 second batch size
    val ssc = new StreamingContext("local[*]", "KafkaExample", Seconds(1))
    
    setupLogging()
    
    // Construct a regular expression (regex) to extract fields from raw Apache log lines
    val pattern = apacheLogPattern()

    // hostname:port for Kafka brokers, not Zookeeper
    val kafkaParams = Map[String, Object](
      "bootstrap.servers" -> "localhost:9092,anotherhost:9092",
      "key.deserializer" -> classOf[StringDeserializer],
      "value.deserializer" -> classOf[StringDeserializer],
      "group.id" -> "example",
      "auto.offset.reset" -> "latest",
      "enable.auto.commit" -> (false: java.lang.Boolean)
    )
    // List of topics you want to listen for from Kafka
    val topics = List("test").toSet
    // Create our Kafka stream, which will contain (topic,message) pairs. We tack a 
    // map(_._2) at the end in order to only get the messages, which contain individual
    // lines of data.
//    val lines = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](
//      ssc, kafkaParams, topics).
//      map(_._2)
    val lines = KafkaUtils.createDirectStream[String, String](
      ssc,
      PreferConsistent,
      Subscribe[String, String](topics, kafkaParams)
    ).map(_.value());
     
    // Extract the request field from each log line
    val requests = lines.map(x => {val matcher:Matcher = pattern.matcher(x); if (matcher.matches()) matcher.group(5)})
    
    // Extract the URL from the request
    val urls = requests.map(x => {val arr = x.toString().split(" "); if (arr.size == 3) arr(1) else "[error]"})
    
    // Reduce by URL over a 5-minute window sliding every second
    val urlCounts = urls.map(x => (x, 1)).reduceByKeyAndWindow(_ + _, _ - _, Seconds(300), Seconds(2))
    
    // Sort and print the results
    val sortedResults = urlCounts.transform(rdd => rdd.sortBy(x => x._2, false))
    sortedResults.print()
    
    // Kick it off
    ssc.checkpoint("/Users/user/Desktop/spark_code/checkpoin")
    ssc.start()
    ssc.awaitTermination()
  }
}



   

Utilities.scala:


package com.sundogsoftware.sparkstreaming

import org.apache.log4j.Level
import java.util.regex.Pattern
import java.util.regex.Matcher

object Utilities {
    /** Makes sure only ERROR messages get logged to avoid log spam. */
  def setupLogging() = {
    import org.apache.log4j.{Level, Logger}   
    val rootLogger = Logger.getRootLogger()
    rootLogger.setLevel(Level.ERROR)   
  }
  
  /** Configures Twitter service credentials using twiter.txt in the main workspace directory */
  def setupTwitter() = {
    import scala.io.Source
    
    for (line <- Source.fromFile("../twitter.txt").getLines) {
      val fields = line.split(" ")
      if (fields.length == 2) {
        System.setProperty("twitter4j.oauth." + fields(0), fields(1))
      }
    }
  }
  
  /** Retrieves a regex Pattern for parsing Apache access logs. */
  def apacheLogPattern():Pattern = {
    val ddd = "\\d{1,3}"                      
    val ip = s"($ddd\\.$ddd\\.$ddd\\.$ddd)?"  
    val client = "(\\S+)"                     
    val user = "(\\S+)"
    val dateTime = "(\\[.+?\\])"              
    val request = "\"(.*?)\""                 
    val status = "(\\d{3})"
    val bytes = "(\\S+)"                     
    val referer = "\"(.*?)\""
    val agent = "\"(.*?)\""
    val regex = s"$ip $client $user $dateTime $request $status $bytes $referer $agent"
    Pattern.compile(regex)    
  }
}



添加完成后,需要下载一个log文件来进行测设:https://raw.githubusercontent.com/zhuweili/MEAN_tinyurl/master/access_log.txt


将其内容复制到本地保存, 文件名为access_log.txt。





为了测试, 先运行 KafkaExample.scala,带其启动后,使用kafka来把刚才的access_log.txt传给spark:



$ kafka-console-producer --broker-list localhost:9092 --topic test < /Users/user/Desktop/spark_code/access_log.txt


这里 /Users/user/Desktop/spark_code/access_log.txt 是我的文件路径, 改成你本地的文件路径便可。


好了, 在Intellij中的输出便可看到spark streaming在处理了,速度很快, 一般一到两个batch便可处理完



到此,spark straeming + kafka的开发环境搭建完成, enjoy。