一、概述
Kafka是一个分布式的消息队列系统(Message Queue)。
- kafka集群有多个Broker服务器组成,每个类型的消息被定义为topic。
- 同一topic内部的消息按照一定的key和算法被分区(partition)存储在不同的Broker上。
- 消息生产者producer和消费者consumer可以在多个Broker上生产/消费topic。
1.1、Topics and Logs:
- Topic即为每条发布到Kafka集群的消息都有一个类别,topic在Kafka中可以由多个消费者订阅、消费。
- 每个topic包含一个或多个partition(分区),partition数量可以在创建topic时指定,每个分区日志中记录了该分区的数据以及索引信息。如下图:
- Kafka只保证一个分区内的消息有序,不能保证一个主题的不同分区之间的消息有序。如果你想要保证所有的消息都绝对有序可以只为一个主题分配一个分区。(分区内有序,一个主题topic不一定是有序的)
- 分区会给每个消息记录分配一个顺序ID号(偏移量), 能够唯一地标识该分区中的每个记录。Kafka集群保留所有发布的记录,不管这个记录有没有被消费过,Kafka提供相应策略通过配置从而对旧数据处理。
- 实际上,每个消费者唯一保存的元数据信息就是消费者当前消费日志的位移位置。位移位置是由消费者控制,即:消费者可以通过修改偏移量读取任何位置的数据。
1.2、Producers -- 生产者
消息生产者,自己决定往哪个partition中写入数据
- 1、基于轮询的负载均衡
- 2、基于hash的partition策略
指定topic来发送消息到Kafka Broker
1.3、Consumers -- 消费者
- 根据topic消费相应的消息
- 消息消费者,自己在zookeeper中维护offset
- 每个消费者都有自己的消费者组,同一个topic中的数据只能在相同的消费组内消费一次,topic的每个partition只能同时被一个消费者组内的消费者消费
- 不同的消费者组之间消费相同的topic会不影响
1.4、broker:组成kafka集群的节点,没有主从关系,依靠zookeeper协调,broker负责消息的读写,存储。一个broker可以管理多个partition。
1.5、topic:消息队列,一类消息。topic由partition组成,一个topic有多少个partition?创建可以指定
1.6、partition
- 组成topic的单元,相当于一个文件,一个partition归一个broker管理,每个partition有副本,有多少个?创建指定
- 一个partition对应一个broker,一个broker可以管多个partition,比如说,topic有6个partition,有两个broker,那每个broker就管3个partition。
- 消息直接写入文件(partition可以很简单想象为一个文件,当数据发过来的时候它就往这个partition上面append,追加就行),并不是存储在内存中;根据时间策略(默认一周)删除,而不是消费完就删除
1.7、zookeeper的作用:
1、存储原数据
- topic
- partition
- broker
2、存储consumer的offsets
二、Kafka特点
- 消息系统的特点:生存者消费者模型,FIFO –––– partition内部是FIFO的,partition之间呢不是FIFO的,当然我们可以把topic设为一个partition,这样就是严格的FIFO
- 高性能:单节点支持上千个客户端,百MB/s吞吐
- 持久性:消息直接持久化在普通磁盘上且性能好 –––– 直接写到磁盘里面去,就是直接append到磁盘里面去,这样的好处是直接持久话,数据不会丢,第二个好处是顺序写,然后消费数据也是顺序的读,所以持久化的同时还能保证顺序读写
- 分布式:数据副本冗余、流量负载均衡、可扩展 –––– 分布式,数据副本,也就是同一份数据可以到不同的broker上面去,也就是当一份数据,磁盘坏掉的时候,数据不会丢失,比如3个副本,就是在3个机器磁盘都坏掉的情况下数据才会丢。
- 很灵活:消息长时间持久化+Client维护消费状态 –––– 消费方式非常灵活,第一原因是消息持久化时间跨度比较长,一天或者一星期等,第二消费状态自己维护消费到哪个地方了,可以自定义消费偏移量
kafka与其他消息队列对比 :
- RabbitMQ:分布式,支持多种MQ协议,重量级
- ActiveMQ:与RabbitMQ类似
- ZeroMQ:以库的形式提供,使用复杂,无持久化
- redis:单机、纯内存性好,持久化较差
- kafka:分布式,较长时间持久化,高性能,轻量灵活
1、页缓存技术 + 磁盘顺序写:操作系统本身有一层缓存,叫做page cache,是在内存里的缓存,我们也可以称之为os cache,意思就是操作系统自己管理的缓存。
2、零拷贝技术:不需要把os cache里的数据拷贝到应用缓存,再从应用缓存拷贝到Socket缓存了,两次拷贝都省略了,所以叫做零拷贝。
三、集群安装
3.1、集群规划:
Zookeeper集群共三台服务器,分别为:node03、node04、node05。
Kafka集群共三台服务器,分别为:node03、node04、node05。
3.2、安装Kafka: tar -zxvf kafka_2.10-0.9.0.1.tgz -C /opt/
3.3、修改配置文件:config/server.properties
- 节点编号:(不同节点按0,1,2,3整数来配置)
# The id of the broker. This must be set to a unique integer for each broker.
broker.id=0
- 真实数据存储位置:
# A comma seperated list of directories under which to store log files
log.dirs=/opt/huawei/kafka-logs
- zookeeper的节点:
# Zookeeper connection string (see zookeeper docs for details).
# This is a comma separated host:port pairs, each corresponding to a zk
# server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002".
# You can also append an optional chroot string to the urls to specify the
# root directory for all kafka znodes.
zookeeper.connect=node03:2181,node04:2181,node05:2181
核心配置参数说明:broker.id: broker集群中唯一标识id,0、1、2、3依次增长(broker即Kafka集群中的一台服务器)
注:当前Kafka集群共三台节点,分别为:node03、node04、node05。对应的broker.id分别为0、1、2。
3.4、节点分发
[root@node03 huawei]# scp -r kafka_2.10-0.8.2.2/ node04:`pwd`
[root@node03 huawei]# scp -r kafka_2.10-0.8.2.2/ node05:`pwd`
3.5、 启动kakka集群
注:先启动Zookeeper集群,再启动Kafka集群。
bin/kafka-server-start.sh config/server.properties
优化:分别在三台服务器上执行以下命令启动:
后台启动:nohup bin/kafka-server-start.sh config/server.properties > kafka.log 2>&1 &
可以创建个脚本:(放在与bin同一级别下,注意创建后要修改权限:chmod 755 startkafka.sh)
测试:
优化设置:设置数据过期时间
无论消息是否被消费,kafka都会保留所有消息。有两种策略可以删除旧数据:
1)基于时间:log.retention.hours=168
2)基于大小:log.retention.bytes=1073741824
kafka默认的只保存7天的数据,时间一到就删除数据,当遇到磁盘过小,存放的数据量过大,可以设置缩短这个时间。
设置全局配置
log.retention.hours=72
log.cleanup.policy=delete
对单独的某一个topic设置过期的时间
./kafka-config.sh --zookeeper localhost:2181 --alter --entity-name mytopic --entity_type topics --add-config retention.ms=86400000
查看设置
./kafka-config --zookeeper localhsot:2181 --describle --entity-name mytopic --entity_type topics
立即删除某个topic下面的数据
./kafka-topic.sh --zookeeper localhost:2181 --alter --topic mytopic --config cleanup.policy=delete
彻底删除topic:
1、删除kafka存储目录(server.properties文件log.dirs配置,默认为"/tmp/kafka-logs")相关topic目录
2、如果配置了delete.topic.enable=true直接通过命令删除,如果命令删除不掉,直接通过zookeeper-client 删除掉broker下的topic即可。
参考:kafka之 Broker 保存消息_qq_43193797的博客-CSDN博客
四、Kafka常用命令
(kafka-topics.sh --help查看帮助手册)
4.1、创建和查看topic
./bin/kafka-topics.sh --zookeeper node03:2181,node04:2181,node05:2181 --create --replication-factor 2 --partitions 3 --topic lxk
./kafka-topics.sh --bootstrap-server 10.1.193.39:6677,10.1.193.40:6677,10.1.193.41:6677 --create --replication-factor 2 --partitions 3 --topic lxk
- --replication-factor:指定每个分区的复制因子个数,默认1个
- --partitions:指定当前创建的kafka分区数量,默认为1个
- --topic:指定新建topic的名称
bin/kafka-topics.sh --zookeeper node03:2181,node04:2181,node05:2181 --list
bin/kafka-topics.sh --zookeeper node03:2181,node04:2181,node05:2181 --describe --topic lxk
./kafka-topics.sh --bootstrap-server 10.1.193.39:6677,10.1.193.40:6677,10.1.193.41:6677 --list
./kafka-topics.sh --bootstrap-server 10.1.193.39:6677,10.1.193.40:6677,10.1.193.41:6677 --describe --topic lxk
4.2、生产者发送消息
bin/kafka-console-producer.sh --broker-list node03:9092,node04:9092,node05:9092 --topic lxk
./kafka-console-producer.sh --broker-list 10.1.193.39:6677,10.1.193.40:6677,10.1.193.41:6677 --topic wireless5GAlarmTopic
4.3、消费者 接受消息
bin/kafka-console-consumer.sh --zookeeper node03:2181,node04:2181,node05:2181 --from-beginning --topic lxk
./kafka-console-consumer.sh --bootstrap-server 10.1.193.39:6677,10.1.193.40:6677,10.1.193.41:6677 --lxk
指定消费者组:
bin/kafka-console-consumer.sh --zookeeper node03:2181,node04:2181,node05:2181
--topic lxk20191108 --consumer.cofig consumer.properties
注:查看帮助手册 bin/kafka-console-consumer.sh help (默认更加key的hash值分区,只有value默认key为null,默认10min换一个分区)
4.4、删除kafka中的数据
① :在kafka集群中删除topic,当前topic被标记成删除。
./kafka-topics.sh --zookeeper node03:2181,node04:2181,node05:2181 --delete --topic t0425
在每台broker节点上删除当前这个topic对应的真实数据。
② :进入zookeeper客户端,删除topic信息
rmr /brokers/topics/t0425
③ :删除zookeeper中被标记为删除的topic信息
rmr /admin/delete_topics/t0425
4.5、kafka的leader的均衡机制
当一个broker停止或者crashes时,所有本来将它作为leader的分区将会把leader转移到其他broker上去,极端情况下,会导致同一个leader管理多个分区,导致负载不均衡,同时当这个broker重启时,如果这个broker不再是任何分区的leader,kafka的client也不会从这个broker来读取消息,从而导致资源的浪费。
kafka中有一个被称为优先副本(preferred replicas)的概念。如果一个分区有3个副本,且这3个副本的优先级别分别为0,1,2,根据优先副本的概念,0会作为leader 。当0节点的broker挂掉时,会启动1这个节点broker当做leader。当0节点的broker再次启动后,会自动恢复为此partition的leader。不会导致负载不均衡和资源浪费,这就是leader的均衡机制。
在配置文件conf/ server.properties中配置开启(默认就是开启):auto.leader.rebalance.enable true
五、Kafka的配置文件
在kafka/config/目录下面有3个配置文件:
- producer.properties
- consumer.properties
- server.properties
5.1、producer.properties:生产端的配置文件
#指定kafka节点列表,用于获取metadata,不必全部指定
#需要kafka的服务器地址,来获取每一个topic的分片数等元数据信息。
metadata.broker.list=kafka01:9092,kafka02:9092,kafka03:9092
#生产者生产的消息被发送到哪个block,需要一个分组策略。
#指定分区处理类。默认kafka.producer.DefaultPartitioner,表通过key哈希到对应分区
#partitioner.class=kafka.producer.DefaultPartitioner
#生产者生产的消息可以通过一定的压缩策略(或者说压缩算法)来压缩。消息被压缩后发送到broker集群,#而broker集群是不会进行解压缩的,broker集群只会把消息发送到消费者集群,然后由消费者来解压缩。
#是否压缩,默认0表示不压缩,1表示用gzip压缩,2表示用snappy压缩。
#压缩后消息中会有头来指明消息压缩类型,故在消费者端消息解压是透明的无需指定。
#文本数据会以1比10或者更高的压缩比进行压缩。
compression.codec=none
#指定序列化处理类,消息在网络上传输就需要序列化,它有String、数组等许多种实现。
serializer.class=kafka.serializer.DefaultEncoder
#如果要压缩消息,这里指定哪些topic要压缩消息,默认empty,表示不压缩。
#如果上面启用了压缩,那么这里就需要设置
#compressed.topics=
#这是消息的确认机制,默认值是0。在面试中常被问到。
#producer有个ack参数,有三个值,分别代表:
#(1)不在乎是否写入成功;
#(2)写入leader成功;
#(3)写入leader和所有副本都成功;
#要求非常可靠的话可以牺牲性能设置成最后一种。
#为了保证消息不丢失,至少要设置为1,也就
#是说至少保证leader将消息保存成功。
#设置发送数据是否需要服务端的反馈,有三个值0,1,-1,分别代表3种状态:
#0: producer不会等待broker发送ack。生产者只要把消息发送给broker之后,就认为发送成功了,这是第1种情况;
#1: 当leader接收到消息之后发送ack。生产者把消息发送到broker之后,并且消息被写入到本地文件,才认为发送成功,这是第二种情况;#-1: 当所有的follower都同步消息成功后发送ack。不仅是主的分区将消息保存成功了,#而且其所有的分区的副本数也都同步好了,才会被认为发动成功,这是第3种情况。
request.required.acks=0
#broker必须在该时间范围之内给出反馈,否则失败。
#在向producer发送ack之前,broker允许等待的最大时间 ,如果超时,
#broker将会向producer发送一个error ACK.意味着上一次消息因为某种原因
#未能成功(比如follower未能同步成功)
request.timeout.ms=10000
#生产者将消息发送到broker,有两种方式,一种是同步,表示生产者发送一条,broker就接收一条;#还有一种是异步,表示生产者积累到一批的消息,装到一个池子里面缓存起来,再发送给broker,#这个池子不会无限缓存消息,在下面,它分别有一个时间限制(时间阈值)和一个数量限制(数量阈值)的参数供我们来设置。#一般我们会选择异步。
#同步还是异步发送消息,默认“sync”表同步,"async"表异步。异步可以提高发送吞吐量,#也意味着消息将会在本地buffer中,并适时批量发送,但是也可能导致丢失未发送过去的消息
producer.type=sync
#在async模式下,当message被缓存的时间超过此值后,将会批量发送给broker,
#默认为5000ms
#此值和batch.num.messages协同工作.
queue.buffering.max.ms = 5000
#异步情况下,缓存中允许存放消息数量的大小。
#在async模式下,producer端允许buffer的最大消息量
#无论如何,producer都无法尽快的将消息发送给broker,从而导致消息在producer端大量沉积
#此时,如果消息的条数达到阀值,将会导致producer端阻塞或者消息被抛弃,默认为10000条消息。
queue.buffering.max.messages=20000
#如果是异步,指定每次批量发送数据量,默认为200
batch.num.messages=500
#在生产端的缓冲池中,消息发送出去之后,在没有收到确认之前,该缓冲池中的消息是不能被删除的,#但是生产者一直在生产消息,这个时候缓冲池可能会被撑爆,所以这就需要有一个处理的策略。#有两种处理方式,一种是让生产者先别生产那么快,阻塞一下,等会再生产;另一种是将缓冲池中的消息清空。
#当消息在producer端沉积的条数达到"queue.buffering.max.meesages"后阻塞一定时间后,#队列仍然没有enqueue(producer仍然没有发送出任何消息)
#此时producer可以继续阻塞或者将消息抛弃,此timeout值用于控制"阻塞"的时间
#-1: 不限制阻塞超时时间,让produce一直阻塞,这个时候消息就不会被抛弃
#0: 立即清空队列,消息被抛弃
queue.enqueue.timeout.ms=-1
#当producer接收到error ACK,或者没有接收到ACK时,允许消息重发的次数
#因为broker并没有完整的机制来避免消息重复,所以当网络异常时(比如ACK丢失)
#有可能导致broker接收到重复的消息,默认值为3.
message.send.max.retries=3
#producer刷新topic metada的时间间隔,producer需要知道partition leader
#的位置,以及当前topic的情况
#因此producer需要一个机制来获取最新的metadata,当producer遇到特定错误时,
#将会立即刷新
#(比如topic失效,partition丢失,leader失效等),此外也可以通过此参数来配置
#额外的刷新机制,默认值600000
topic.metadata.refresh.interval.ms=60000
5.2、consumer.properties:消费端的配置文件
#消费者集群通过连接Zookeeper来找到broker。
#zookeeper连接服务器地址
zookeeper.connect=zk01:2181,zk02:2181,zk03:2181
#zookeeper的session过期时间,默认5000ms,用于检测消费者是否挂掉
zookeeper.session.timeout.ms=5000
#当消费者挂掉,其他消费者要等该指定时间才能检查到并且触发重新负载均衡
zookeeper.connection.timeout.ms=10000
#这是一个时间阈值。
#指定多久消费者更新offset到zookeeper中。
#注意offset更新时基于time而不是每次获得的消息。
#一旦在更新zookeeper发生异常并重启,将可能拿到已拿到过的消息
zookeeper.sync.time.ms=2000
#指定消费
group.id=xxxxx
#这是一个数量阈值,经测试是500条。
#当consumer消费一定量的消息之后,将会自动向zookeeper提交offset信息#注意offset信息并不是每消费一次消息就向zk提交
#一次,而是现在本地保存(内存),并定期提交,默认为true
auto.commit.enable=true
# 自动更新时间。默认60 * 1000
auto.commit.interval.ms=1000
# 当前consumer的标识,可以设定,也可以有系统生成,
#主要用来跟踪消息消费情况,便于观察
conusmer.id=xxx
# 消费者客户端编号,用于区分不同客户端,默认客户端程序自动产生
client.id=xxxx
# 最大取多少块缓存到消费者(默认10)
queued.max.message.chunks=50
# 当有新的consumer加入到group时,将会reblance,此后将会
#有partitions的消费端迁移到新 的consumer上,如果一个
#consumer获得了某个partition的消费权限,那么它将会向zk
#注册 "Partition Owner registry"节点信息,但是有可能
#此时旧的consumer尚没有释放此节点, 此值用于控制,
#注册节点的重试次数.
rebalance.max.retries=5
#每拉取一批消息的最大字节数
#获取消息的最大尺寸,broker不会像consumer输出大于
#此值的消息chunk 每次feth将得到多条消息,此值为总大小,
#提升此值,将会消耗更多的consumer端内存
fetch.min.bytes=6553600
#当消息的尺寸不足时,server阻塞的时间,如果超时,
#消息将立即发送给consumer
#数据一批一批到达,如果每一批是10条消息,如果某一批还
#不到10条,但是超时了,也会立即发送给consumer。
fetch.wait.max.ms=5000
socket.receive.buffer.bytes=655360
# 如果zookeeper没有offset值或offset值超出范围。
#那么就给个初始的offset。有smallest、largest、
#anything可选,分别表示给当前最小的offset、
#当前最大的offset、抛异常。默认largest
auto.offset.reset=smallest
# 指定序列化处理类
derializer.class=kafka.serializer.DefaultDecoder
5.3、server.properties:服务端的配置文件
#broker的全局唯一编号,不能重复
broker.id=0
#用来监听链接的端口,producer或consumer将在此端口建立连接
port=9092
#处理网络请求的线程数量,也就是接收消息的线程数。
#接收线程会将接收到的消息放到内存中,然后再从内存中写入磁盘。
num.network.threads=3
#消息从内存中写入磁盘是时候使用的线程数量。
#用来处理磁盘IO的线程数量
num.io.threads=8
#发送套接字的缓冲区大小
socket.send.buffer.bytes=102400
#接受套接字的缓冲区大小
socket.receive.buffer.bytes=102400
#请求套接字的缓冲区大小
socket.request.max.bytes=104857600
#kafka运行日志存放的路径
log.dirs=/export/servers/logs/kafka
#topic在当前broker上的分片个数
num.partitions=2
#我们知道segment文件默认会被保留7天的时间,超时的话就
#会被清理,那么清理这件事情就需要有一些线程来做。这里就是
#用来设置恢复和清理data下数据的线程数量
num.recovery.threads.per.data.dir=1
#segment文件保留的最长时间,默认保留7天(168小时),
#超时将被删除,也就是说7天之前的数据将被清理掉。
log.retention.hours=168
#滚动生成新的segment文件的最大时间
log.roll.hours=168
#日志文件中每个segment的大小,默认为1G
log.segment.bytes=1073741824
#上面的参数设置了每一个segment文件的大小是1G,那么
#就需要有一个东西去定期检查segment文件有没有达到1G,
#多长时间去检查一次,就需要设置一个周期性检查文件大小
#的时间(单位是毫秒)。
log.retention.check.interval.ms=300000
#日志清理是否打开
log.cleaner.enable=true
#broker需要使用zookeeper保存meta数据
zookeeper.connect=zk01:2181,zk02:2181,zk03:2181
#zookeeper链接超时时间
zookeeper.connection.timeout.ms=6000
#上面我们说过接收线程会将接收到的消息放到内存中,然后再从内存
#写到磁盘上,那么什么时候将消息从内存中写入磁盘,就有一个
#时间限制(时间阈值)和一个数量限制(数量阈值),这里设置的是
#数量阈值,下一个参数设置的则是时间阈值。
#partion buffer中,消息的条数达到阈值,将触发flush到磁盘。
log.flush.interval.messages=10000
#消息buffer的时间,达到阈值,将触发将消息从内存flush到磁盘,
#单位是毫秒。
log.flush.interval.ms=3000
#删除topic需要server.properties中设置delete.topic.enable=true否则只是标记删除
delete.topic.enable=true
#此处的host.name为本机IP(重要),如果不改,则客户端会抛出:
#Producer connection to localhost:9092 unsuccessful 错误!
host.name=kafka01
advertised.host.name=192.168.239.128
六、整合Java API
package com.lxk.sparkstreaming;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
import java.util.Random;
import kafka.javaapi.producer.Producer;
import kafka.producer.KeyedMessage;
import kafka.producer.ProducerConfig;
import kafka.serializer.StringEncoder;
/**
* 向kafka中生产数据
*/
public class SparkStreamingDataManuallyProducerForKafka extends Thread {
public static void main(String[] args) {
new SparkStreamingDataManuallyProducerForKafka("t1017").start();
}
static String[] channelNames = new String[] { "Spark", "Scala", "Kafka", "Flink", "Hadoop", "Storm", "Hive",
"Impala", "HBase", "ML" };
static String[] actionNames = new String[] { "View", "Register" };
private String topic; // 发送给Kafka的数据,topic
private Producer<String, String> producerForKafka;
private static String dateToday;
private static Random random;
public SparkStreamingDataManuallyProducerForKafka(String topic) {
dateToday = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
this.topic = topic;
random = new Random();
Properties properties = new Properties();
properties.put("metadata.broker.list", "node03:9092,node04:9092,node05:9092");
// 发送消息key的编码格式
properties.put("key.serializer.class", StringEncoder.class.getName());
// 发送消息value的编码格式
properties.put("serializer.class", StringEncoder.class.getName());
producerForKafka = new Producer<String, String>(new ProducerConfig(properties));
}
@Override
public void run() {
int counter = 0;
int flagNum = 0;
while (true) {
counter++;
flagNum++;
String userLog = userlogs();
System.out.println("product:" + userLog + " ");
producerForKafka.send(new KeyedMessage<String, String>(topic,"key_"+flagNum, userLog));
// producerForKafka.send(new KeyedMessage<String, String>(topic, userLog));
// 每两条数据暂停2秒
if (0 == counter % 2) {
counter = 0;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 生成随机数据
private static String userlogs() {
StringBuffer userLogBuffer = new StringBuffer("");
int[] unregisteredUsers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
long timestamp = new Date().getTime();
Long userID = 0L;
long pageID = 0L;
// 随机生成的用户ID
if (unregisteredUsers[random.nextInt(8)] == 1) {
userID = null;
} else {
userID = (long) random.nextInt(2000);
}
// 随机生成的页面ID
pageID = random.nextInt(2000);
// 随机生成Channel
String channel = channelNames[random.nextInt(10)];
// 随机生成action行为
String action = actionNames[random.nextInt(2)];
userLogBuffer.append(dateToday).append("\t").append(timestamp).append("\t").append(userID).append("\t")
.append(pageID).append("\t").append(channel).append("\t").append(action);
// .append("\n");
return userLogBuffer.toString();
}
}
结果:
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
product:2019-10-17 1571323543254 891 610 Storm Register
product:2019-10-17 1571323543383 1155 1962 Kafka Register
product:2019-10-17 1571323545385 1915 1932 Hadoop Register
product:2019-10-17 1571323545386 352 897 HBase Register
product:2019-10-17 1571323547389 741 1802 Spark Register
product:2019-10-17 1571323547390 1081 1721 Impala Register
product:2019-10-17 1571323549391 null 627 Impala View
product:2019-10-17 1571323549392 null 730 Flink View
product:2019-10-17 1571323551394 1566 1639 Spark Register