Kafka
一、概述
- kafka是什么
- 在流式计算中,kafka一般用来缓存数据,Storm通过消费kafka的数据来进行计算
- Apache kafka是一个开源的消息系统,有Scala写成,是由Apache软件基金会开发的一个开源消息系统项目
- kafka最初由LinkedIn开发,并与2001年初开源,2012年10月从Apache Incubator毕业,改项目的目标是为处理实时数据提供一个统一,高通量、低等待平台
- Kafka是一个分布式消息队列,Kafka对消息保存时根据Topic进行归类,发送消息这成为Producer,消息接收者称为Consumer,此外Kafka集群由多个Kafka实例组成,每个实例(server)称为broker
- 无论Kafka集群还是Producer和Consumer都依赖与Zookeeper集群保存一些meta信息,来保证系统可用性
- kafka内部原理
- 点对点模式: 一对一,消费者主动拉去数据,消息收到后消息清除
- 点对点模型通常是一个基于拉取或者轮询的消息传送模型,这种模型从队列中请求信息,而不是将信息推送到客户端。这个模型的特点是发送到队列的消息被一个且只有一个接收者处理,即使有多个消息监听者也是如此
- 发布订阅模式: 一对多,数据生产后,推送给所有订阅者
- 发布订阅模型则是一个基于推送的消息传送模型。发布订阅模型可以有多重不同订阅者,临时订阅者只在主动监听主题时才接受消息,而持久订阅者则监听主题的所有消息,即使当前订阅者不可用,处理离线状态
- 点对点模式: 一对一,消费者主动拉去数据,消息收到后消息清除
- 为什么需要消息队列
- 解耦:
- 允许你独立的扩展或修改两边的处理过程,只要保证他们遵守同样的接口约束
- 冗余:
- 消息队列把数据进行持久化直到他们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列采用的 插入-获取-删除 范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的支出改消息已经被出里完毕,从而确保你的数据被安全的保存直到你使用完毕
- 扩展性:
- 因为消息队列解耦了你的处理过程,所以增大消息入队和出里的频率是很容易的,只要另外增加处理过程即可
- 灵活性 & 峰值处理:
- 在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷请求而完全崩溃。
- 可恢复性:
- 系统的一部分失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统回复后处理。
- 顺序保证:
- 在大多使用场景下,数据出里的额顺序都很重要。不部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理, kafka保证一个partition内的消息的有序性。
- 缓冲:
- 有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况
- 异步通信:
- 很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户吧一个消息放入队列,但并不立即处理它。想向列队中放入多少消息就放多少,然后在需要的时候再处理他们。
- 解耦:
- Kafka架构
- Producer 消息生产者,就是向kafka broker 发消息的客户端
- Consumer 消息消费者,想kafka broker 去消息的客户端
- Topic 一个队列
- Consumer Group (GC): 这是Kafka用来实现一个topic消息的广播(发给所有的Consumer)和单播(发给任意一个consumer)的手段。一个topic可以有多个GC。topic的消息会复制(不是真的复制,是概念上的)到所有的GC,但是每个partion只会吧消息发给改GC中的一个consumer。如果需要实现广播,只要每一个consumer有一个独立的gc就可以了。要实现单播只要所有的consumer在同一个gc。用gc还可以将consumer进行自由的分组而不需要多次发送消息到不同的topic。
- broker 一台kafka服务器就一个broker。一个进群由多个broker组成。一个broker可以容纳多个toppic。
- partition 为了实现扩展性,一个非常大的topic可以分布到多个broker上, 一个topic可以分为多个partition,每个partition是一个有序的队列。partition中的每条消息都会被分配一个有序的id(offset)。kafka只保证一个partition中的顺序将消息发给consumer。不保证一个topic的整体的顺序。
- offset kafka的储存文件都是按照offset.kafka 来命名,用offset做名字的好处是方便查找。例如你想找位于2049的位置,只要找到2048.kafka的文件即可,当然the first offset就是 00000000.kafka
二、kafka工作流程分析
- kafka生产过程分析
- 写入方式: producer采用推 (push)模式将消息发不到broker,每条消息都被追加(append)到分区(partition)中,属于顺序写磁盘(顺序写磁盘效率比随机写内存要高,保证kafka吞吐率)。
- 分区(partition):消息发送时都被发送到一个topic,其本质就是一个目录,而topic是由一些partition logs分区日志组成,其组织结构如下图所示
-
每个partition中的消息都是有序的,生产的消息被不断追加到partition log上,其中的每一个消息都被赋予了一个唯一的 offset 值
-
分区的原因:
-
方便集群中扩展,每个partition可以通过调整以适应它所在机器,而一个topic又可以有多个Partition组成,因此整个集群就可以适应任意大小的数据了。
-
可以提高并发量,因为可以以partitio为单位续写了
-
-
分区原则
-
指定了 partition 则直接使用
-
未指定partition但是指定key 通过key的value 进行hash出一个partition
-
partition 和key 都未指定,使用轮询选出一个partition
-
DefaultPartitioner类 public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) { List<PartitionInfo> partitions = cluster.partitionsForTopic(topic); int numPartitions = partitions.size(); if (keyBytes == null) { int nextValue = nextValue(topic); List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic); if (availablePartitions.size() > 0) { int part = Utils.toPositive(nextValue) % availablePartitions.size(); return availablePartitions.get(part).partition(); } else { // no partitions are available, give a non-available partition return Utils.toPositive(nextValue) % numPartitions; } } else { // hash the keyBytes to choose a partition return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions; } }
-
-
-
副本
-
同一个partition可能会有多个replicaton (对应server.propertise 配置中的default.replication.factor = n )。 没有replication 的情况下,一旦broker宕机,其上所有partition的数据都不能被消费,同时,producer也不能在将数据存于其上的 partition。引入replication之后,同有一个partition可能会有多个replication,而这时需要在这些replication之间选出一个leader,producer和consumer 只与这个leader交互,其他replication作为follower从leader中复制数据。
-
-
写入流程
-
producer 先从zookeeper的 "/brokers/../state"节点找到该partition的leader
-
producer 将消息发送给改leader
-
leader将消息写入本地log
-
followers 从leader pull 消息,写入本地log后想leader 发送 ack
-
leader收到所有ISR中的replication的ack后,增加HW(high watermark 最后commit的offset) 并向producer发送ack
-
-
broker 保存消息
-
存储方式
-
物理上把topic分成一个或多个partition (对应server.producer中的 number.partition = 3 配置), 每个partition物理上对应一个文件夹(该文件夹储存改partition的所有消息和索引文件)
-
-
存储策略
-
无论消息是否被消费,kafka都会保留所有信息,有两种策略可以删除就数据:
-
基于时间:log.retention.hours = 168
- 基于大小:log.retention.bytes = 1073741824
-
-
-
因为kafka读取特定消息的时间负责度为O(1),即与文件大小无关,所以这里删除过期文件与提高kafka性能无关
-
- zookeeper 储存结构
- kafka消费过程分析
kafka提供了两套API: 高级consumer api 和低级api
-
- 高级api
- 优点:
- 高级api
不需要自行去管理offset 系统过zookeeper自行管理
不需要管理分区,副本等情况,系统自动管理
消费者断线会自动根据上一次记录在zookeeper中的offset去接着获取数据,默认设置1分钟更新一下zookeeper中存的offset
可以使用group来分区对同一个topic的不同程序访问分离开来(不同的group记录不同的offset,这样不同程序读取同一个topic才不会因为offset互相影响)
-
-
- 缺点:
-
不能自行控制offset 对于某些特殊需要来说
不能细化控制如分区,副本 zk等
-
- 低级api
- 优点
- 低级api
能够开发者自己控制offset 想从哪里读就从哪里读
自行控制连接分区,对分区自定义进行负载均衡
对zookeeper的依赖性降低, 如 offset 不一定非要开zk储存,自行储存offset即可 比如存在文件或者内存中
-
-
- 缺点
-
太过复杂,需要自行控制offset 连接那个分区, 找到分区leader等
-
- 消费者组
消费者是以consumer group 消费者组的方式工作,有一个或者多个消费这组成一个组,共同消费一个topic 每个分区在同一个时间只能由group中的一个消费者读取,但是多个group可以同时消费这个partition。在图中,有一个由三个消费者组成的group,有一个消费者读取主题中的两个分区,两个分别去读一个分区。某个消费者读取某个分区,也可以叫做某个消费者是某个分区的拥有者。
这种情况下,消费者可以通过水平扩展的方式同时被读取大量的消息。 另外,如果一个消费者失败了,那么其他的group成员会自动负载均衡读取之前失败的消费者读取的分区。
-
- 消费方式
consumer 采用 pull 模式从broker中读取数据
push (推) 模式很难适应消费速率不同的消费者,因为消息发送速率是由broker决定的。他的目标是尽可能以最快速度传递消息,但是这样很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而pull 模式则可以根据consumer的消费能力以适当的速率消费消息。
对于kafka而言,pull模式更合适,他可简化broker的设计,consumer可自主控制消费消息的速率,同事consumer可以自己控制消费方式,即可批量消费也可逐条消费,同事还能选择不同的提交方式从而实现不同的传输语义。
-
- 消费组案例
- 需求: 测试同一个消费组中的消费者,同一时刻只能由一个消费者消费。
- 案例: 略
- 消费组案例
三、kafka拦截器 (interceptor)
- 拦截器原理
producer 拦截器 (interceptor)在kafka 0.1.0 版本被引入的,主要用于实现clients端的定制化控制逻辑
对于producer而言,interceptor是的用户在消息发送以前及producer回调逻辑前有机会对消息做一些定制化需求,比如修改消息等。同事,producer 允许用户指定多个interceptor按顺序用于同一条消息从而形成一个拦截链
interceptor实现接口是 org.apache.kafka.clients.producer.ProducerInterceptor, 其定义的方法包括:
configure(configs): 获取配置信息和舒适化数据时调用
onSend(producerRecord) 该方法封装进KafkaProducer.send 方法中,即他运行在用户主线程中,Producer确保在消息被序列化以计算分区前调用该方法。 用户可以在改方法中对消息做任何操作,但最好保证不要修改消息所属的topic和分区, 否则会影响目标分区的计算。
onAcknewledgement(RecordMetadata, Exception): 该方法会在消息被应答之前或消息发送失败时调用,并且通常都是在producer回调逻辑出发之前。onAcknewledgement运行在producer的IO线程中,一次不要在该方法中放入很重的逻辑,否则会拖慢producer的消息发送效率
close() : 关闭 interceptor 主要用于执行一些资源清理工作
如上所述,interceptor可以运行在多个线程中,因此在具体实现时用户需要自行确保线程安全,另外倘若指定多了个interceptor 则producer 将安装指定顺序调用他们,并仅仅是捕获每个interceptor 可能抛出的异常记录到错误日志中而非向上传递。 在这使用过程中要特别留意。
四、 kafka streams
- kafka streams
- kafka streams apache kafka 开源项目的一个组成部分。 是一个功能强大,易于使用的库。用户在kafka上构建高分布式 扩展性 容错的应用程序。
- 特点
- 功能强大: 高扩展性, 弹性, 容错
- 轻量级: 无需专门的集群, 一个库 而不是框架····
- 完全集成: 百分百与kafka版本兼容,易于集成到现有的应用程序
- 实时性: 毫秒级延迟,并非微批处理,窗口允许乱序数据,允许迟到数据
- 为什么要有kafka stream
当前已经有非常多的流式处理系统,最知名且应用最多的开源流式处理系统有 spark streaming 和 apache storm。 apache storm 发展多年,应用广泛,提供记录级别的处理能力,当前也支持sql on stream。 而spark streaming 基于 apache spark , 可以非常方便与图计算,sql 处理等集成,功能强大, 对于数据其他spark 应用开发的用户而言使用门槛低,另外 目前主流的hadoop 发行版,都集成了apache storm 和apache spark,使得部署更容易
既然Apache spark 与apache storm 拥有如此多的优势,那为何还需要kafka stream 呢 ?
第一:spark 和 storm都是流式处理框架, 而kafka stream 提供的是一个机遇kafka 的流失处理类库。 框架要求开发者必须按照特定的方式去开发逻辑部分,供框架调用。开发者很难了解框架的具体运行方式,从而使得调试成本高,并且使用受限。而kafka stream 作为流式处理类库,直接提供具体的类给开发者调用,整个应用的额运行方式主要由开发者控制,方便使用和调试。
第二: 虽然Cloudera与hortonworks 方便了storm和spark 的部署,但是这些框架的部署仍然相对复杂,而kafka stream 作为类库,可以非常方便的其纳入应用程序中,它对应用的打包和部署基本没有任何要求。
第三: 就流式处理系统而言,基本都是支持kafka作为数据源,例如storm具有专门的 kafka-spout,而spark也提供专门的spark+streaming+kafka模块。事实上,kafka基本上是主流的流式处理系统的标准数据源。换言之,大部分流式系统中都已部署了kafka,此时使用kafka stream的成本非常低。
第四: 使用storm 或spark streaming时,需要为框架本身的进程预留资源,如storm的supervisor和spark on yarn 的node manager。 即使对于应用而言,框架本身也会占用部分资源,如spark streaming 需要为shuffle 和 storage 预留内存。 但是kafka 作为类库不占用系统资源。
第五: 由于kafka 本身提供数据持久化,因此kafka stream提供滚动 部署和滚动升级以及重新计算的能力。
第六: 由于kafka conusmer rebalance机制,kafka stream 可以在线动态调整并行度。
五、集群部署
- kakfa集群部署
- 配置静态ip
- 安装jdk
- 安装zookeeper
- 解压安装包
- 修改配置 config/server.properties文件
#broker的全局唯一编号,不能重复 broker.id=0 #删除topic功能使能 delete.topic.enable=true #处理网络请求的线程数量 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=/opt/kafka/logs #topic在当前broker上的分区个数 num.partitions=1 #用来恢复和清理data下数据的线程数量 num.recovery.threads.per.data.dir=1 #segment文件保留的最长时间,超时将被删除 log.retention.hours=168 #配置连接Zookeeper集群地址 zookeeper.connect=hadoop102:2181,hadoop103:2181,hadoop104:2181
-
- 配置环境变量
vim /etc/profile #kafka_home export KAFKA_HOME=/opt/kafka export PATH=$PATH:$KAFKA_HOME/bin source /etc/profile
-
- 将kafka目录复制到集群所有机器上 并以此修改 config/servier.properties 将broker.id 修改成唯一的值
- 以此启动集群
- bin/kafka-server-start.sh config/server.properties &
- kafka配置信息
- broker 配置信息
属性 | 默认值 | 描述 |
broker.id | 必填参数 broker 的唯一标识 | |
log.dirs | /tmp/kafka-logs | kafka数据存放的目录。可以指定多个目录,中间用逗号分割,当新partition被创建的时候会被存放当前存放partition最少的目录 |
port | 9092 | brokerServer接受客户端连接的端口号 |
zookeeper.connect | null | zookeeper的连接串,格式为hostname1:port1,hostname2:port2。 可以天一个或多个,为了提供可靠性,建议都填上。次配置允许我们制定一个zookeeper路径来存放次kafka集群的所有数据为了与其他应用集群区分开,建议在此配置中制定本集群存放目录格式为 hostname:port1,hostname2:port2,hostname3:port3/chroot/path.需要注意的是,消费者参数要和此参数一致 |
message.max.bytes | 1000000 | 服务器可以接收到的醉倒的消息大小,注意此参数和consumer的maximum.message.size大小一致,否则会因为生产者生产的消息太大导致消费者无法消费 |
num.io.threads | 8 | 服务器用来执行读写请求的IO线程数,次参数的数量至少要等于服务器上的磁盘的数量 |
queued.max.requests | 500 | I/O线程可以处理请求的队列大小,若实际请求书超过次大小,网络线程将停止接受新的请求 |
soket.request.max.bytes | 100* 1024*1024 | 服务器匀速请求的最大值,用来防止内存溢出, 其值应该小于java heap size |
num.partitions | 1 | 默认partiton数量,如果topic在创建时没有指定partition数量,默认使用此值。 |
log.segment.bytes | 1024*1024*1024 | Segment文件的大小,超过此值将会自动新建一个segment,此值可以被topic级别的参数覆盖 |
log.roll.{ms,hours} | 24*7hours | 新建segment文件的时间,此值可以topic级别的参数覆盖 |
log.retention.{ms,minute,hours} | 7days | kafka segment log 的保存周期,保存周期超过此时间日志就被会删除,此参数可以被topic级别参数覆盖,数据量大时建议减小此值 |
log.retention.bytes | -1 | 每个partition的最大容量,若数据量超过此值 partition的数据将会被删除,注意这个参数是控制每个partition而不是topic 次参数可以被log级别参数覆盖 |
log.retention.check.inteval.ms | 5minutes | 删除策略的检查周期 |
auto.create.topic.enable | true | 自动创建topic参数,建议此参数设置为false,严格控制topic管理, 防止生产者错写topic |
default.replication.factor | 1 | 默认副本数量,建议改为2 |
replica.lag.time.max.ms | 10000 | 在此窗口时间内没有收到follower的fetch请求, leader 会将其从ISR(in-sync replicas)中移除 |
replica.lag.max.messages | 4000 | 如果replica节点落后leader节点此值大小的消息数量,leader节点就会将其从isr中移除 |
replica.socket.timeout.ms | 30 * 1000 | replica向leader发送请求的超时时间 |
zookeeper.session.timeout.ms | 6000 | zookeeper session 超时时间,如果在此时间内server没有向zookeeper发送心跳,zookeeper就会认为此节点已经挂掉,此值太低导致节点容易被标记死亡,诺太高会导致太迟发现死亡 |
zookeeper.connection.timeout.ms | 6000 | zookeerper session 超时时间 |
zookeeper.sync.time.ms | 2000 | zk follower 落后zk leader 时间 |
controlled.shutdown.enble | true | 允许broker shutdown 如果启动,broker在关闭自己之前会把它上面所有leaders转移到其他brrokers上,建议启用 增强集群稳定性 |
-
- Producer配置信息
属性 | 默认值 | 描述 |
metadata.brokerr.list | 启动时producer查询brokers的列表,可以是集群中所有brokers的一个子集。注意,这个参数只是用来获取topic的元信息用,producer会从元信息中挑选合适的broker并与简历socket链接。格式:host1:port1,host2:port2 | |
request.timeout.ms | 10000 | broker等待ack的超时时间,若等待时间超过此值,会返回客户端错误信息 |
producer.type | sync | 同步异步模式。async表示异步,sync表示同步。如果设置成异步模式,可以允许生产者以batch的形式push数据,这样会极大的提高broker性能,推荐设置为异步。 |
serializer.class | kafka.serializer.DefaultEncoder | 序列化列,默认序列化成 byte[] |
prttitioner.class | kafka.producer.DufaultPartitioner | Partition类, 默认对key |
compression.codec | none | 指定producer消息的压缩格式,可选参数为 none, gzip, snappy |
compressed.topic | null | 启用压缩的topic名称,若上面参数选择了一个压缩格式,那么压缩仅对参数指定的topic有效,若本参数为空,则对所有topic有效 |
message.send.max.retries | 3 | Producer 发送失败时重试次数,若网络出现出问题, 可能会导致不断重试 |
queue.buffering.max.message | 10000 | 采用异步模式时,producer 队列里面最大缓存消息数量,如果超过这个数值,producer机会堵塞或者丢掉消息 |
queue.enqueue.timeout.ms | -1 | 当达到上面参数值时producer堵塞等待的时间。如果值设置为0 buffer队列满时producer不会堵塞,消息直接被丢掉,若值设置为-1,producer会被堵塞,不会丢消息 |
batch.num.message | 200 | 采用异步模式时,一个batch缓存的消息数量,达到这个数量时producer才会发送消息 |
-
- Consumer 配置参数
属性 | 默认值 | 描述 |
group.id | consumer的组id,相同group.id的consumer属于同一个组 | |
zookeeper.connect | consumer的zookeeper连接串,要和berker的配置一致 | |
consumer.id | null | 如果不设置会自动生成 |
socket.timeout.ms | 30*1000 | 网络请求的socket超时时间,实际超时时间与max.fetch.wait + socket.timeout.ms确定 |
fetch.message.max.bytes | 1024*1024 | 查询topic-partition时允许的最大消息大小。consumer会为每个partition缓存次大小的消息到内存,因此,这个参数可以控制consumer的内存使用量,这个值应该至少比server允许的最大消息大小大,以免producer发送的消息大于consumer允许的消息 |
auto.commit.interval.ms | 60 | Consumer提交offset值到zookeeper的周期 |
aoto.commit.enable | true | 如果此值为true,consumer会周期性的把当前消费的offset保存到zookeeper 当 consumer 失败重启之后会使用此值作为新开始消费的值 |
queued.max.message.chunks | 2 | 用来被consumer消费的message chunks数量,每个chunk可以缓存fetch.message.max.bytes 大小的数据量 |
六、API
- 创建生产者(过时的api)
package com.atguigu.kafka; import java.util.Properties; import kafka.javaapi.producer.Producer; import kafka.producer.KeyedMessage; import kafka.producer.ProducerConfig; public class OldProducer { @SuppressWarnings("deprecation") public static void main(String[] args) { Properties properties = new Properties(); properties.put("metadata.broker.list", "hadoop102:9092"); properties.put("request.required.acks", "1"); properties.put("serializer.class", "kafka.serializer.StringEncoder"); Producer<Integer, String> producer = new Producer<Integer,String>(new ProducerConfig(properties)); KeyedMessage<Integer, String> message = new KeyedMessage<Integer, String>("first", "hello world"); producer.send(message ); } }
- 创建生产者(新api)
package com.atguigu.kafka; import java.util.Properties; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; public class NewProducer { public static void main(String[] args) { Properties props = new Properties(); // Kafka服务端的主机名和端口号 props.put("bootstrap.servers", "hadoop103:9092"); // 等待所有副本节点的应答 props.put("acks", "all"); // 消息发送最大尝试次数 props.put("retries", 0); // 一批消息处理大小 props.put("batch.size", 16384); // 请求延时 props.put("linger.ms", 1); // 发送缓存区内存大小 props.put("buffer.memory", 33554432); // key序列化 props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); // value序列化 props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); Producer<String, String> producer = new KafkaProducer<>(props); for (int i = 0; i < 50; i++) { producer.send(new ProducerRecord<String, String>("first", Integer.toString(i), "hello world-" + i)); } producer.close(); } }
- 创建生产者带回调函数(新api)
package com.atguigu.kafka; import java.util.Properties; import org.apache.kafka.clients.producer.Callback; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; public class CallBackProducer { public static void main(String[] args) { Properties props = new Properties(); // Kafka服务端的主机名和端口号 props.put("bootstrap.servers", "hadoop103:9092"); // 等待所有副本节点的应答 props.put("acks", "all"); // 消息发送最大尝试次数 props.put("retries", 0); // 一批消息处理大小 props.put("batch.size", 16384); // 增加服务端请求延时 props.put("linger.ms", 1); // 发送缓存区内存大小 props.put("buffer.memory", 33554432); // key序列化 props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); // value序列化 props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); KafkaProducer<String, String> kafkaProducer = new KafkaProducer<>(props); for (int i = 0; i < 50; i++) { kafkaProducer.send(new ProducerRecord<String, String>("first", "hello" + i), new Callback() { @Override public void onCompletion(RecordMetadata metadata, Exception exception) { if (metadata != null) { System.err.println(metadata.partition() + "---" + metadata.offset()); } } }); } kafkaProducer.close(); } }
- 自定义分区生产者
package com.atguigu.kafka; import java.util.Map; import kafka.producer.Partitioner; // 需求:将所有数据存储到topic的第0号分区上 // 定义一个类实现Partitioner接口,重写里面的方法(过时API) public class CustomPartitioner implements Partitioner { public CustomPartitioner() { super(); } @Override public int partition(Object key, int numPartitions) { // 控制分区 return 0; } } package com.atguigu.kafka; import java.util.Map; import org.apache.kafka.clients.producer.Partitioner; import org.apache.kafka.common.Cluster; // 新api public class CustomPartitioner implements Partitioner { @Override public void configure(Map<String, ?> configs) { } @Override public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) { // 控制分区 return 0; } @Override public void close() { } } package com.atguigu.kafka; import java.util.Properties; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; // 代码调用 public class PartitionerProducer { public static void main(String[] args) { Properties props = new Properties(); // Kafka服务端的主机名和端口号 props.put("bootstrap.servers", "hadoop103:9092"); // 等待所有副本节点的应答 props.put("acks", "all"); // 消息发送最大尝试次数 props.put("retries", 0); // 一批消息处理大小 props.put("batch.size", 16384); // 增加服务端请求延时 props.put("linger.ms", 1); // 发送缓存区内存大小 props.put("buffer.memory", 33554432); // key序列化 props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); // value序列化 props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); // 自定义分区 props.put("partitioner.class", "com.atguigu.kafka.CustomPartitioner"); Producer<String, String> producer = new KafkaProducer<>(props); producer.send(new ProducerRecord<String, String>("first", "1", "atguigu")); producer.close(); } }
- Kafka消费者api
- 旧版api
package com.atguigu.kafka.consume; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import kafka.consumer.Consumer; import kafka.consumer.ConsumerConfig; import kafka.consumer.ConsumerIterator; import kafka.consumer.KafkaStream; import kafka.javaapi.consumer.ConsumerConnector; public class CustomConsumer { @SuppressWarnings("deprecation") public static void main(String[] args) { Properties properties = new Properties(); properties.put("zookeeper.connect", "hadoop102:2181"); properties.put("group.id", "g1"); properties.put("zookeeper.session.timeout.ms", "500"); properties.put("zookeeper.sync.time.ms", "250"); properties.put("auto.commit.interval.ms", "1000"); // 创建消费者连接器 ConsumerConnector consumer = Consumer.createJavaConsumerConnector(new ConsumerConfig(properties)); HashMap<String, Integer> topicCount = new HashMap<>(); topicCount.put("first", 1); Map<String, List<KafkaStream<byte[], byte[]>>> consumerMap = consumer.createMessageStreams(topicCount); KafkaStream<byte[], byte[]> stream = consumerMap.get("first").get(0); ConsumerIterator<byte[], byte[]> it = stream.iterator(); while (it.hasNext()) { System.out.println(new String(it.next().message())); } } }
-
- 新版api
package com.atguigu.kafka.consume; import java.util.Arrays; import java.util.Properties; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; public class CustomNewConsumer { public static void main(String[] args) { Properties props = new Properties(); // 定义kakfa 服务的地址,不需要将所有broker指定上 props.put("bootstrap.servers", "hadoop102:9092"); // 制定consumer group props.put("group.id", "test"); // 是否自动确认offset props.put("enable.auto.commit", "true"); // 自动确认offset的时间间隔 props.put("auto.commit.interval.ms", "1000"); // key的序列化类 props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); // value的序列化类 props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); // 定义consumer KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props); // 消费者订阅的topic, 可同时订阅多个 consumer.subscribe(Arrays.asList("first", "second","third")); while (true) { // 读取数据,读取超时时间为100ms ConsumerRecords<String, String> records = consumer.poll(100); for (ConsumerRecord<String, String> record : records) System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value()); } } }