Kafka是个什么东西
– kafka是一个高吞吐的分部式消息系统
kafka的特点 :
– 解耦
– 缓冲
官网:https://kafka.apache.org/
kafka集群有多个Broker服务器组成,每个类型的消息被定义为topic。
同一topic内部的消息按照一定的key和算法被分区(partition)存储在不同的Broker上。
消息生产者producer和消费者consumer可以在多个Broker上生产/消费topic
概念理解:
Topics and Logs:
Topic即为每条发布到Kafka集群的消息都有一个类别,topic在Kafka中可以由多个消费者订阅、消费。
每个topic包含一个或多个partition(分区),partition数量可以在创建topic时指定,每个分区日志中记录了该分区的数据以及索引信息。如下图:
Kafka只保证一个分区内的消息有序,不能保证一个主题的不同分区之间的消息有序。如果你想要保证所有的消息都绝对有序可以只为一个主题分配一个分区。
分区会给每个消息记录分配一个顺序ID号(偏移量), 能够唯一地标识该分区中的每个记录。Kafka集群保留所有发布的记录,不管这个记录有没有被消费过,Kafka提供相应策略通过配置从而对旧数据处理。
实际上,每个消费者唯一保存的元数据信息就是消费者当前消费日志的位移位置。位移位置是由消费者控制,即、消费者可以通过修改偏移量读取任何位置的数据。
Distribution – 分布式
Producers – 生产者
指定topic来发送消息到Kafka Broker
Consumers – 消费者
根据topic消费相应的消息
集群规划:
Zookeeper集群共三台服务器,分别为:node01、node02、node03。(这是我的集群)
Kafka集群共三台服务器,分别为:node01、node02、node03。
1、Zookeeper集群准备
kafka是一个分布式消息队列,需要依赖ZooKeeper,请先安装好zk集群。
Zookeeper集群安装步骤略。
2、安装Kafka
下载压缩包(官网地址:http://kafka.apache.org/downloads.html)
解压:
tar zxvf kafka_2.10-0.9.0.1.tgz -C /opt/
mv kafka_2.10-0.9.0.1/ kafka
修改配置文件:config/server.properties
核心配置参数说明:
broker.id: broker集群中唯一标识id,0、1、2、3依次增长(broker即Kafka集群中的一台服务器)
注:
当前Kafka集群共三台节点,分别为:node01、node02、node03。对应的broker.id分别为0、1、2。
zookeeper.connect: zk集群地址列表(我的是node01、node02、node03)
将当前node01服务器上的Kafka目录同步到其他node02、node03服务器上:
scp -r /opt/kafka/ node02:/opt
scp -r /opt/kafka/ node03:/opt
修改node02、node03上Kafka配置文件中的broker.id(分别在node02、03服务器上执行以下命令修改broker.id)
sed -i -e ‘s/broker.id=./broker.id=1/’ /opt/kafka/config/server.properties
sed -i -e 's/broker.id=./broker.id=2/’ /opt/kafka/config/server.properties
3、启动Kafka集群
A、启动Zookeeper集群。
B、启动Kafka集群。
分别在三台服务器上执行以下命令启动:
bin/kafka-server-start.sh config/server.properties
创建话题
(kafka-topics.sh --help查看帮助手册)
创建topic:
bin/kafka-topics.sh --zookeeper node01:2181,node02:2181,node03:2181 --create --replication-factor 2 --partitions 3 --topic test
(参数说明:
–replication-factor:指定每个分区的复制因子个数,默认1个
–partitions:指定当前创建的kafka分区数量,默认为1个
–topic:指定新建topic的名称)
查看topic列表:
bin/kafka-topics.sh --zookeeper node01:2181,node02:2181,node03:2181 --list
查看“test”topic描述:
bin/kafka-topics.sh --zookeeper node01:2181,node02:2181,node03:2181 --describe --topic test
创建生产者:
bin/kafka-console-producer.sh --broker-list node01:9092,node02:9092,node03:9092 --topic test
创建消费者:
bin/kafka-console-consumer.sh --zookeeper node01:2181,node02:2181,node03:2181 --from-beginning --topic test
注:
查看帮助手册:
bin/kafka-console-consumer.sh help
hive的安装模式有三种,这里我们就介绍一种多用户模式
Remote一体
这种存储方式需要在远端服务器运行一个mysql服务器,并且需要在Hive服务器启动meta服务。
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>hive.metastore.warehouse.dir</name>
<value>/user/hive/warehouse</value>
</property>
//数据库地址
<property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:mysql://192.168.0.10:3306/hive?createDatabaseIfNotExist=true</value>
</property>
<property>
<name>javax.jdo.option.ConnectionDriverName</name>
<value>com.mysql.jdbc.Driver</value>
</property>
//数据库用户
<property>
<name>javax.jdo.option.ConnectionUserName</name>
<value>hive</value>
</property>
//数据库密码
<property>
<name>javax.jdo.option.ConnectionPassword</name>
<value>password</value>
</property>
<property>
<name>hive.metastore.local</name>
<value>false</value>
</property>
<property>
<name>hive.metastore.uris</name>
<value>thrift://192.168.0.20:9083</value>
</property>
</configuration>
注:这里把hive的服务端和客户端都放在同一台服务器上了。服务端和客户端可以拆开,
2.Remote分开
将hive-site.xml配置文件拆为如下两部分
1)服务端配置文件
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>hive.metastore.warehouse.dir</name>
<value>/user/hive/warehouse</value>
</property>
<property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:mysql://192.168.0.10:3306/hive?createDatabaseIfNotExist=true</value>
</property>
<property>
<name>javax.jdo.option.ConnectionDriverName</name>
<value>com.mysql.jdbc.Driver</value>
</property>
//数据库用户
<property>
<name>javax.jdo.option.ConnectionUserName</name>
<value>root</value>
</property>
//数据库密码
<property>
<name>javax.jdo.option.ConnectionPassword</name>
<value>123456</value>
</property>
</configuration>
2)、客户端配置文件
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>hive.metastore.warehouse.dir</name>
<value>/user/hive/warehouse</value>
</property>
<property>
<name>hive.metastore.local</name>
<value>false</value>
</property>
//hive的metastore的地址
<property>
<name>hive.metastore.uris</name>
<value>thrift://192.168.0.10:9083</value>
</property>
</configuration>
启动hive服务端程序
hive --service metastore
客户端直接使用hive命令即可
root@node02:~$ hive
hive> show tables;
OK
test_hive
Time taken: 0.736 seconds
hive>
客户端启动的时候要注意:
[ERROR] Terminal initialization failed; falling back to unsupported
java.lang.IncompatibleClassChangeError: Found class jline.Terminal, but interface was expected
at jline.TerminalFactory.create(TerminalFactory.java:101)
错误的原因: Hadoop jline版本和hive的jline不一致
SparkStreaming是流式处理框架,是Spark API的扩展,支持可扩展、高吞吐量、容错的实时数据流处理,实时数据的来源可以是:Kafka, Flume, Twitter, ZeroMQ或者TCP sockets,并且可以使用高级功能的复杂算子来处理流数据。例如:map,reduce,join,window 。最终,处理后的数据可以存放在文件系统,数据库等,方便实时展现。
SparkSreaming的安装我们这里也就不介绍,如果有需要的同学可以下面评论有时间会为大家介绍下安装的目录的。嘻嘻嘻
下面我们直接上代码:
package com.gcy.scala.spark.OnKafKa
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, Row, SparkSession}
import org.apache.spark.sql.types._
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka010.{CanCommitOffsets, HasOffsetRanges, KafkaUtils, OffsetRange}
import org.apache.spark.streaming.kafka010.LocationStrategies.PreferConsistent
import org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe
import org.apache.spark.streaming.{Durations, StreamingContext}
/**
* SparkStreaming2.3版本 读取kafka 中数据 :
* 1.采用了新的消费者api实现,类似于1.6中SparkStreaming 读取 kafka Direct模式。并行度 一样。
* 2.因为采用了新的消费者api实现,所有相对于1.6的Direct模式【simple api实现】 ,api使用上有很大差别。未来这种api有可能继续变化
* 3.kafka中有两个参数:
* heartbeat.interval.ms:这个值代表 kafka集群与消费者之间的心跳间隔时间,kafka 集群确保消费者保持连接的心跳通信时间间隔。这个时间默认是3s.
* 这个值必须设置的比session.timeout.ms appropriately 小,一般设置不大于 session.timeout.ms appropriately 的1/3
* session.timeout.ms appropriately:
* 这个值代表消费者与kafka之间的session 会话超时时间,如果在这个时间内,kafka 没有接收到消费者的心跳【heartbeat.interval.ms 控制】,
* 那么kafka将移除当前的消费者。这个时间默认是10s。
* 这个时间位于配置 group.min.session.timeout.ms【6s】 和 group.max.session.timeout.ms【300s】之间的一个参数,如果SparkSteaming 批次间隔时间大于5分钟,
* 也就是大于300s,那么就要相应的调大group.max.session.timeout.ms 这个值。
* 4.大多数情况下,SparkStreaming读取数据使用 LocationStrategies.PreferConsistent 这种策略,这种策略会将分区均匀的分布在集群的Executor之间。
* 如果Executor在kafka 集群中的某些节点上,可以使用 LocationStrategies.PreferBrokers 这种策略,那么当前这个Executor 中的数据会来自当前broker节点。
* 如果节点之间的分区有明显的分布不均,可以使用 LocationStrategies.PreferFixed 这种策略,可以通过一个map 指定将topic分区分布在哪些节点中。
*
* 5.新的消费者api 可以将kafka 中的消息预读取到缓存区中,默认大小为64k。默认缓存区在 Executor 中,加快处理数据速度。
* 可以通过参数 spark.streaming.kafka.consumer.cache.maxCapacity 来增大,也可以通过spark.streaming.kafka.consumer.cache.enabled 设置成false 关闭缓存机制。
*
* 6.关于消费者offset
* 1).如果设置了checkpoint ,那么offset 将会存储在checkpoint中。
* 这种有缺点: 第一,当从checkpoint中恢复数据时,有可能造成重复的消费,需要我们写代码来保证数据的输出幂等。
* 第二,当代码逻辑改变时,无法从checkpoint中来恢复offset.
* 2).依靠kafka 来存储消费者offset,kafka 中有一个特殊的topic 来存储消费者offset。新的消费者api中,会定期自动提交offset。这种情况有可能也不是我们想要的,
* 因为有可能消费者自动提交了offset,但是后期SparkStreaming 没有将接收来的数据及时处理保存。这里也就是为什么会在配置中将enable.auto.commit 设置成false的原因。
* 这种消费模式也称最多消费一次,默认sparkStreaming 拉取到数据之后就可以更新offset,无论是否消费成功。自动提交offset的频率由参数auto.commit.interval.ms 决定,默认5s。
* 如果我们能保证完全处理完业务之后,可以后期异步的手动提交消费者offset.
*
* 3).自己存储offset,这样在处理逻辑时,保证数据处理的事务,如果处理数据失败,就不保存offset,处理数据成功则保存offset.这样可以做到精准的处理一次处理数据。
*注释:这里的解释是北京尚学堂的我敬佩的大佬讲师的
*/
object SparkStreamingOnKafkaDirect {
def main(args: Array[String]): Unit = {
// val conf = new SparkConf()
// conf.setMaster("local")
// conf.setAppName("SparkStreamingOnKafkaDirect")
val spark = SparkSession.builder().appName("test").master("local").enableHiveSupport().getOrCreate()
val ssc = new StreamingContext(spark.sparkContext,Durations.seconds(3))
//设置日志级别
ssc.sparkContext.setLogLevel("Error")
val kafkaParams = Map[String, Object](
"bootstrap.servers" -> "node01:9092,node02:9092,node03:9092",
"key.deserializer" -> classOf[StringDeserializer],
"value.deserializer" -> classOf[StringDeserializer],
"group.id" -> "MyGroupId",//
/**
* 当没有初始的offset,或者当前的offset不存在,如何处理数据
* earliest :自动重置偏移量为最小偏移量
* latest:自动重置偏移量为最大偏移量【默认】
* none:没有找到以前的offset,抛出异常
*/
"auto.offset.reset" -> "earliest",
/**
* 当设置 enable.auto.commit为false时,不会自动向kafka中保存消费者offset.需要异步的处理完数据之后手动提交
*/
"enable.auto.commit" -> (false: java.lang.Boolean)//默认是true
)
//设置Kafka的topic
val topics = Array("test")
//创建与Kafka的连接,接收数据
/*这里接收到数据的样子
2019-09-26 1569487411604 1235 497 Kafka Register
2019-09-26 1569487411604 1235 497 Kafka Register
2019-09-26 1569487414838 390 778 Flink View
*/
val stream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
ssc,
PreferConsistent,//
Subscribe[String, String](topics, kafkaParams)
)
//对接收到的数据进行处理,打印出来接收到的key跟value,最后放回的是value
val transStrem: DStream[String] = stream.map(record => {
val key_value = (record.key, record.value)
println("receive message key = "+key_value._1)
println("receive message value = "+key_value._2)
key_value._2
})
//这里用了一下动态创建的Schema
val structType: StructType = StructType(List[StructField](
StructField("Date_", StringType, nullable = true),
StructField("Timestamp_", StringType, nullable = true),
StructField("UserID", StringType, nullable = true),
StructField("PageID", StringType, nullable = true),
StructField("Channel", StringType, nullable = true),
StructField("Action", StringType, nullable = true)
))
//因为foreachRDD可以拿到封装到DStream中的rdd,可以对里面的rdd进行,
/*代码解释:
先从foreach中拿到一条数据,,在函数map中对接收来的数据用 “\n” 进行切分,放到Row中,用的是动态创建Schema,因为我们需要再将数据存储到hive中,所以需要Schema。
因为map是transformance算子,所以用rdd.count()触发一下
spark.createDataFrame:创建一个DataFrame,因为要注册一个临时表,必须用到DataFrame
frame.createOrReplaceTempView("t1"):注册临时表
spark.sql("use spark"):使用 hive 的 spark 库
result.write.mode(SaveMode.Append).saveAsTable("test_kafka"):将数据放到 test_kafka 中
*/
transStrem.foreachRDD(one=>{
val rdd : RDD[Row] = one.map({
a =>
val arr = a.toString.split("\t")
Row(arr(0).toString, arr(1).toString, arr(2).toString, arr(3).toString, arr(4).toString, arr(5).toString)
})
rdd.count()
val frame : DataFrame = spark.createDataFrame(rdd,structType)
// println(" Scheme: "+frame.printSchema())
frame.createOrReplaceTempView("t1")
// spark.sql("select * from t1").show()
spark.sql("use spark")
result.write.mode(SaveMode.Append).saveAsTable("test_kafka")
}
)
/**
* 以上业务处理完成之后,异步的提交消费者offset,这里将 enable.auto.commit 设置成false,就是使用kafka 自己来管理消费者offset
* 注意这里,获取 offsetRanges: Array[OffsetRange] 每一批次topic 中的offset时,必须从 源头读取过来的 stream中获取,不能从经过stream转换之后的DStream中获取。
*/
stream.foreachRDD { rdd =>
val offsetRanges: Array[OffsetRange] = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
// some time later, after outputs have completed
stream.asInstanceOf[CanCommitOffsets].commitAsync(offsetRanges)
}
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}
以上就是SparkStreaming+Kafka+hive的代码,还有许多需要优化的地方,希望同学们多多指出。
上面创建数据的代码的有需要的可以下面评论发给大家,今天就到这里哦!