kafka+python安装及使用教程
前几天学习kafka,从网上搜索了很多教程发现写的都比较肤浅,真正生产环境下是无法使用的,因为会有故障的情况。经过各种摸索以及查阅官方文档,总结了下面这一套教程,最大的改进是使用手动commit消费结果,并且写了一个直观的demo用于理解kafka offset,希望能帮助大家节省学习时间。有问题的话可以留言,题主看到会尽量回复。
安装kafka
使用docker部署kafka 3.4版本,最新版的kafka已经不需要zookeeper作为依赖
当然官方也保留了zookeeper的支持,具体部署方法可以查看https://hub.docker.com/r/bitnami/kafka。
为了开发方便,我们直接用一个kafka镜像即可。
创建文件docker-compose.yml:
version: "2"
services:
broker:
container_name: broker
image: docker.io/bitnami/kafka:3.4
ports:
- "9092:9092"
volumes:
- "kafka_data:/bitnami"
environment:
- ALLOW_PLAINTEXT_LISTENER=yes
- KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 # 使用者要用localhost为名才能访问
- KAFKA_BROKER_ID=1
- KAFKA_CFG_log_retention_hours=1 # KAFKA_CFG_ + kafka 配置参数,无需映射配置文件
volumes:
kafka_data:
driver: local
注意 KAFKA_CFG_log_retention_hours=1 配置了一个小时前的数据都会清除,可以自己设置大一些的数字
其他配置参数都可以写成KAFKA_CFG_ + kafka配置的形式,会自动覆盖默认参数
启动:
docker compose up -d
python代码
首先安装官方维护的库
pip install confluent-kafka
kafka管理者脚本
kafka_admin.py
from confluent_kafka.admin import AdminClient
from confluent_kafka.admin import NewTopic,NewPartitions
from confluent_kafka.admin._metadata import ClusterMetadata
admin = AdminClient({'bootstrap.servers':'localhost:9092'})
# 创建一个topic
print(admin.create_topics(new_topics=[NewTopic('purchases',-1)]))
# 删除一个topic
# print(admin.delete_topics(topics=['new-topic']))
# 列出所有的topic
topics:ClusterMetadata = admin.list_topics()
print(topics.topics) # 默认有一个__consumer_offsets ,用前缀过滤掉
print(admin.list_consumer_groups())
# admin.create_partitions(NewPartitions(topic='purchases',new_total_count=3)) # new_total_count 新增分片的数量到这个数,而不是新增的数量
生产者
producer.py
#!/usr/bin/env python
import sys
from random import choice
from argparse import ArgumentParser, FileType
from configparser import ConfigParser
from confluent_kafka import Producer
if __name__ == '__main__':
config = {'bootstrap.servers': 'localhost:9092'}
producer = Producer(config)
# Optional per-message delivery callback (triggered by poll() or flush())
# when a message has been successfully delivered or permanently
# failed delivery (after retries).
def delivery_callback(err, msg):
if err:
print('ERROR: Message failed delivery: {}'.format(err))
else:
print("Produced event to topic {topic}: key = {key:12} value = {value:12}".format(
topic=msg.topic(), key=msg.key().decode('utf-8'), value=msg.value().decode('utf-8')))
# Produce data by selecting random values from these lists.
topic = "purchases"
user_ids = ['eabara', 'jsmith', 'sgarcia', 'jbernard', 'htanaka', 'awalther']
products = ['book', 'alarm clock', 't-shirts', 'gift card', 'batteries']
count = 0
for _ in range(10):
user_id = choice(user_ids)
product = choice(products)
producer.produce(topic, product, user_id, callback=delivery_callback)
count += 1
print(len(producer)) # 查询当前有多少个等待传送给kafka的消息
# 直到消息发送成功
producer.poll(10000) # 向kafka推送消息
print(len(producer)) # 这里应该是0
producer.flush()
创建消费者
consumer.py
#!/usr/bin/env python
import sys
from argparse import ArgumentParser, FileType
from configparser import ConfigParser
from confluent_kafka import Consumer, OFFSET_BEGINNING, TopicPartition, Message
def do_consume(msg,consumer):
if msg.error():
print("ERROR: %s".format(msg.error()))
else:
'''在这里消费你的数据'''
print("消费到一条数据 from topic {topic}: key = {key:12} value = {value:12}".format(
topic=msg.topic(), key=msg.key().decode('utf-8'), value=msg.value().decode('utf-8')))
# print('当前的offset',consumer.position([TopicPartition(topic1,0)])) # 获取消费者临时的offset(理解为缓存)
# print('before commit offset',consumer.committed([TopicPartition(topic1,0)])) # 获取kafka中的offset
'''消费成功'''
consumer.commit(msg,asynchronous=False) # offset+1
print('after commit offset',consumer.committed([TopicPartition(topic1,0)])) # 获取kafka中的offset
def poll(consumer):
# Poll for new messages from Kafka and print them.
try:
while True:
msg:Message = consumer.poll(1.0) # 消费一个单消息
if msg is None:
# Initial message consumption may take up to
# `session.timeout.ms` for the consumer group to
# rebalance and start consuming
print("Waiting...")
else:
do_consume(msg,consumer)
except KeyboardInterrupt:
pass
finally:
# Leave group and commit final offsets
consumer.close()
if __name__ == '__main__':
consume_from_init = False # 修改为True时,无视已消费的数据,从队头开始消费。否则从最后一个未消费的数据开始
config = {
'bootstrap.servers': 'localhost:9092', # 由于docker中的设置,这里必须是localhost
'group.id': 'python_example_group_1',
'auto.offset.reset': 'earliest',
'enable.auto.commit': 'false' # 关闭自动提交
}
consumer = Consumer(config)
def reset_offset(consumer, partitions):
if consume_from_init:
for p in partitions:
p.offset = OFFSET_BEGINNING
consumer.assign(partitions)
# Subscribe to topic
topic1 = "purchases"
topic2 = 'new-topic'
consumer.subscribe([topic1,topic2], on_assign=reset_offset)
# 获取当前高低水位,高水位代表当前队列里未消费消息数+offset+1 ,可以理解为队列最大长度
low,high = consumer.get_watermark_offsets(TopicPartition(topic1,0)) # topic,partition index
print(low,high)
# 获取当前的offset
broker_offset = consumer.committed([TopicPartition(topic1,0)])
print('当前的offset',broker_offset[0].offset) # 获取kafka中的offset
current_wait_message = high - broker_offset[0].offset # 当前队列中已入队,未消费的消息数
print('当前队列中未消费消息数:',current_wait_message)
poll(consumer)
注意我们需要在配置中将自动提交关闭:’enable.auto.commit’: ‘false’ ,这样才能实现未成功重复消费
注意这一行
current_wait_message = high - broker_offset[0].offset
,我们首先获取到队列的高水位high
,也就是队尾的offset,然后获取了当前的指针位置broker_offset[0].offset
,然后相减,就获得的当前未消费的数据量。这个current_wait_message
可以用于限流(>1000时拒收),故障判断(长时间>1000说明负载不够了)等应用。
使用
- 运行
python admin.py
使用管理者脚本创建一个topicpurchases
,并可以自定义设置分片 - 执行
python consumer.py
打开生产者,如果队列中有数据,会打印到控制台 - 执行一次
python producer.py
,会向kafka推送十条数据,然后消费者就会收到对应的数据并打印出来。