反爬虫项目Day4

Lua集成Kafka实现

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by User.
--- DateTime: 2020/2/27 10:00
---
--[[
脚本对接kafka
]]
-- 指定数据采集脚本运行线程的阈值,若超过这个阈值,则不再采集数据
local DEFAULT_THREADS = 1000

-- broker list
local BROKER_LIST = {
    {host = "*.*.*.*",port = 9092},
    {host = "*.*.*.*",port = 9092},
    {host = "*.*.*.*",port = 9092}
}

-- 设置分区数 分区数一定要和kafka中topic分区数一致
local PARTRTION_NUM = 3

-- kafka中的topic
local TOPIC = "nginxdata"

-- 设置连接参数
local CONNECTION_PARAMS = {
    {producer_type = "async"},
    {socket_timeout = 3000},
    {flush_time = 10000},
    {request_timeout = 20000}
}

--[[
指定分区的过程,不指定分区,默认使用默认分区
]]

-- 自定义共享内存的key(nginx的共享内存)
local POLLING_KEY = "POLLING_KEY"
-- 获取nginx的共享内存
local shared_data = ngx.shared.shared_data
-- 从共享内存中获取POLLING_KEY对应的value值
local POLLING_VAL = shared_data : get(POLLING_KEY)
-- 对获取到的value值进行判断
if not value then
    POLLING_VALUE = 1
    shared_data:set(POLLING_KEY,POLLING_VALUE)
end

-- 计算消息要分发到哪个分区中
local PARTITION_ID = tonumber(POLLING_VALUE) % PARTRTION_NUM
-- 自增key的控制  POLLING_KEY 所对应的value值自增一,使得该key对应的value能够均匀分布到不同的分区中
shared_data:incr(POLLING_KEY,1)

-- 并发控制 次数控制数据采集与否
local isGone = true
-- 获取ngx.var.connections_active进行过载保护,若当前的活跃连接数大于阈值,进行限流保护
if tonumber(nginx.var.connections_active) > tonumber(DEFAULT_THREADS) then
    isGone = false
end

-- 数据的采集  采集后的数据拼接起来,作为message传到kafka中
if isGone then
    local time_local = ngx.var.time_local
    if time_local == nil then
        time_local = ""
    end

    local request = ngx.var.request
    if request == nil then
        request = ""
    end

    local request_method = ngx.var.request_method
    if request_method == nil then
        request_method = ""
    end

    local content_type = ngx.var.content_type
    if content_type == nil then
        content_type = ""
    end

    ngx.req.read_body()
    local request_body = ngx.var.request_body
    if request_body == nil then
        request_body = ""
    end

    local http_referer = ngx.var.http_referer
    if http_referer == nil then
        http_referer = ""
    end

    local remote_addr = ngx.var.remote_addr
    if remote_addr == nil then
        remote_addr = ""
    end

    local http_user_agent = ngx.var.http_user_agent
    if http_user_agent == nil then
        http_user_agent = ""
    end

    local time_iso8601 = ngx.var.time_iso8601
    if time_iso8601 == nil then
        time_iso8601 = ""
    end

    local server_addr = ngx.var.server_addr
    if server_addr == nil then
        server_addr = ""
    end

    local http_cookie = ngx.var.http_cookie
    if http_cookie == nil then
        http_cookie = ""
    end

    --封装数据,将数据封装成message
    local message = time_local .. "#CS#" .. request .. "#CS#" ..
            request_method .. "#CS#" .. content_type .. "#CS#" ..
            request_body .. "#CS#" .. http_referer .. "#CS#" ..
            remote_addr .. "#CS#" .. http_user_agent .. "#CS#" ..
            time_iso8601 .. "#CS#" .. server_addr .. "#CS#" .. http_cookie;

    -- 导包 引入kafkaProducer
    local pro = require "resty.kafka.producer"

    -- 获取producer实例  将数据从lua中的缓存写入到kafka,相对于kafka来说,lua的缓存相当于生产者
    
    -- local cli = client:new(broker_list, producer_config)
    local producer = pro:new(BROKER_LIST,CONNECTION_PARAMS)

    -- 发送数据
    -- function _M.send(self, topic, key, message)
    local ok,error = producer:send(TOPIC,PARTITION_ID,message)
    if not ok then
        ngx.log(ngx.ERR,"kafka send err",error)
    end
end

脚本具体的执行步骤见反爬虫项目Day3。
在kafka的某一个节点上启动消费者,消费指定分区nginxdata,即可验证是否数据成功导入到kafka中。

实现消费kafka数据过程

lua脚本将数据写入到kafka中后,就可以通过sparkStreaming或者flink来消费数据,将数据进行清洗,脱敏(过滤敏感词),分类,解析,加工,结构化后将数据再次写入到kafka中,之后再进行相依的业务逻辑的处理。

Streaming消费kafka两种方式

Streaming消费kafka有两种方式,分别为receiver和direct

Receiver方式是通过 KafkaUtils.createStream() 方法来创建一个DStream对象,它不关注消费位移的处理,Receiver方式的结构如下图所示。但这种方式在Spark任务执行异常时会导致数据丢失的情况,如果要保证数据的可靠性,需要开启预写式日志,简称WAL(Write Ahead Logs),只有接收到的数据被持久化到WAL之后才会去更新Kafka中的消费位移。接收到的数据和WAL存储位置信息被可靠地存储,如果期间出现故障,这些信息被用来从错误中恢复,并继续处理数据。

WAL的方式可以保证从Kafka中接收的数据不被丢失。但是在某些异常情况下,一些数据被可靠地保存到了WAL中,但是还没有来得及更新消费位移,这样会造成Kafka中的数据被Spark拉取了不止一次。同时在Receiver方式中,Spark中的RDD分区和Kafka的分区并不是相关的,因此增加Kafka中主题的分区数并不能增加Spark处理的并行度,而仅是增加接收器接收数据的并行度。

Direct方式是从Spark1.3开始引入的,它通过KafkaUtils.createDirectStream()方法创建一个DStream对象,该方式中Kafka的一个分区与Spark RDD对应,通过定期扫描所订阅Kafka每个主题的每个分区的最新偏移量以确定当前批处理数据偏移范围。与Receiver方式相比,Direct方式不需要维护一份WAL数据,由Spark Streaming程序自己控制位移的处理,通常通过检查点机制处理消费位移,这样可以保证Kafka中的数据只会被Spark拉取一次。

这种方法相较于Receiver方式的优势在于:
简化的并行:在Receiver的方式中我们提到创建多个Receiver之后利用union来合并成一个Dstream的方式提高数据传输并行度。而在Direct方式中,Kafka中的partition与RDD中的partition是一一对应的并行读取Kafka数据,这种映射关系也更利于理解和优化。
高效:在Receiver的方式中,为了达到0数据丢失需要将数据存入Write Ahead Log中,这样在Kafka和日志中就保存了两份数据,浪费!而第二种方式不存在这个问题,只要我们Kafka的数据保留时间足够长,我们都能够从Kafka进行数据恢复。
精确一次:在Receiver的方式中,使用的是Kafka的高阶API接口从Zookeeper中获取offset值,这也是传统的从Kafka中读取数据的方式,但由于Spark Streaming消费的数据和Zookeeper中记录的offset不同步,这种方式偶尔会造成数据重复消费。而第二种方式,直接使用了简单的低阶Kafka API,Offsets则利用Spark Streaming的checkpoints进行记录,消除了这种不一致性。

Streaming Receiver Demo

package sparkStreaming.day2

import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}

/**
 * 基于0.8的receiver
 */
object Demo11_ReceiverDemo {
  def main(args: Array[String]): Unit = {
    //获取Streaming上下文的环境
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("streaming-receiver")
    val sc: StreamingContext = new StreamingContext(conf, Seconds(5))

    //设置检查点,保存状态   一般:放到hdfs中;不需要手动创建
    sc.checkpoint("检查点目录")  //此处用到checkPoint是因为下边用到了 updateStateByKey算子
    //获取kafka的数据流
    //返回值:DStream of (Kafka message key, Kafka message value)
    val kafkaStream: ReceiverInputDStream[(String, String)] = KafkaUtils.createStream(
      sc,
      "master:2181,slave1:2181,slave2:2181/kafka",
      "kafka-streaming",
      Map("streaming" -> 1)
    )
    //数据处理
    //_._2.split(" ") tuples中的第二个是value 第一个是key
    val tuples: DStream[(String, Int)] = kafkaStream.flatMap(_._2.split(" ")).map((_, 1))
    //自定义更新函数
    val updateFunction = (value: Seq[Int], state: Option[Int]) => {
      val sum = value.sum + state.getOrElse(0)
      Option(sum)
    }
    val sumed: DStream[(String, Int)] = tuples.updateStateByKey(updateFunction)
    //将结果打印到控制台
    sumed.print()
    //开启采集器
    sc.start()
    //等待采集
    sc.awaitTermination()
  }
}

Streaming Direct Demo

package sparkStreaming.day2

import kafka.serializer.StringDecoder
import org.apache.spark.{HashPartitioner, SparkConf}
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}

/**
 * 基于0.8的Direct
 */
object Demo12_DirectDemo {
  def main(args: Array[String]): Unit = {
    //获取streaming上下文环境
    val conf: SparkConf = new SparkConf().setAppName("streaming-direct").setMaster("local[*]")
    val sc: StreamingContext = new StreamingContext(conf, Seconds(5))

    //设置检查点,保存状态   一般:放到hdfs中;不需要手动创建
    sc.checkpoint("检查点目录") //此处用到checkPoint是因为下边用到了 updateStateByKey算子 
    //获取数据流
    val kafkaStream: InputDStream[(String, String)] = KafkaUtils.createDirectStream[String,String,StringDecoder,StringDecoder](
      sc,
      Map[String, String]("metadata.broker.list" -> "master:9092,slave1:9092,slave2:9092"),
      Set("streaming")
    )
    //统计
    //@return DStream of (Kafka message key, Kafka message value)
    val tuples: DStream[(String, Int)] = kafkaStream.flatMap(_._2.split(" ")).map((_, 1))
    //自定义更新函数
    val updateFunction = (it : Iterator[(String, Seq[Int], Option[Int])]) =>{
      it.map(x=>{
        (x._1,x._2.sum + x._3.getOrElse(0))
      })
    }
    val sumed: DStream[(String, Int)] = tuples.updateStateByKey(updateFunction, new HashPartitioner(sc.sparkContext.defaultMinPartitions), true)
    //将结果输出到控制台
    sumed.print()
    //开启采集器
    sc.start()
    //等待采集
    sc.awaitTermination()
  }
}

对于消费offset的维护,"enable.auto.commit"该属性设置为true时,自动提交,offset维护在kafka中的topic中(0.9版本之后,0.8版本是维护在zookeeper中的)

消费kafka中的数据-Direct方法

package com.cxl.streamingMonitor.dataprocess.launch

import java.lang

import com.cxl.streamingMonitor.common.util.jedis.PropertiesUtil
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.streaming.dstream.InputDStream
import org.apache.spark.streaming.kafka010.{ConsumerStrategies, KafkaUtils, LocationStrategies}
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.{SparkConf, SparkContext}

/**
 * 数据处理主类
 */
object DataProcessLaunch {



  def main(args: Array[String]): Unit = {
    //当应用被停止的时候,进行如下设置可以保证当前批次执行完之后再停止应用。
    System.setProperty("spark.streaming.stopGracefullyOnShutdown", "true")

    //初始化spark环境
    val conf: SparkConf = new SparkConf().setAppName("DataProcessLaunch").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
//    new StreamingContext(conf,Seconds(5))
    //请求kafka的参数
    // consumer grouping
    val groupid : String = "nginxdata"

    //kafka params
    val kafkaparams: Map[String, Object] = Map[String, Object](
      // 指定消费kafka的ip和端口
      "bootstrap.servers" ->
        PropertiesUtil.getStringByKey("default.brokers", "kafkaConfig.properties"),
      // 设置kafka的解码方式   反射的方式
      "key.deserializer" -> classOf[StringDeserializer],
      "value.deserializer" -> classOf[StringDeserializer],
      "group.id" -> groupid,
      // 从头消费 从最开始
      "auto.offset.reset" -> "earliest",
      //自动提交false
      "enable.auto.commit" -> (false: lang.Boolean)
    )

    //topic
    val topics: Set[String] = Set(PropertiesUtil.getStringByKey("source.nginx.topic", "kafkaConfig.properties"))

    //创建流处理方法
    val ssc: StreamingContext = setupSsc(sc, kafkaparams, topics)
    ssc.start()
    ssc.awaitTermination()

  }


  /**
   * 
   * @param sc
   * @param kafkaparams
   * @param topics
   * @return
   */
  def setupSsc(sc: SparkContext, kafkaparams: Map[String, Object], topics: Set[String]): StreamingContext = {
    //创建streamingContext
    val ssc: StreamingContext = new StreamingContext(sc, Seconds(3))

    //消费kafka数据

    val kafkaStream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream(
      ssc,
      LocationStrategies.PreferConsistent,
      ConsumerStrategies.Subscribe[String, String](topics, kafkaparams)
    )
    kafkaStream.map(_.value()).foreachRDD(rdd => rdd.foreach(println))
    ssc
  }
}

未完待续·····
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值