a distributed streaming platform.
参考 kafka-python官方文档
Kafka简介
Kafka是一种消息队列
,主要用来处理大量数据状态
下的消息队列,一般用来做日志的处理。既然是消息队列,那么Kafka
也就拥有消息队列的相应的特性了。
- 消息队列的好处:
- 解藕
- 异步处理
- 削峰平谷
Kafka的消费模式
- 一对一模式
消息生产者发布消息到Queue队列中,通知消费者从队列中拉取消息进行消费。消息被消费之后则删除,Queue支持多个消费者,但对于一条消息而言,只有一个消费者可以消费,即一条消息只能被一个消费者消费
。 - 一对多模式
这种模式也称为发布/订阅模式,即利用Topic存储消息,消息生产者将消息发布到Topic中,同时有多个消费者订阅此Topic,消费者可以从中消费消息,注意发布到Topic中的消息会被多个消费者消费,消费者消费数据之后,数据不会被清除,Kafka会默认保留一段时间,然后再删除。
Kafka的基础架构
Kafka像其他MQ一样,也有自己的基础架构,主要存在生产者Producer、Kafka集群Broker、消费者Consumer、注册消息Zookeeper。
- Producer:消息生产者,向Kafka中发布消息的角色。
- Consumer:消息消费者,即从Kafka中拉取消息消费的客户端。
- Consumer Group:消费者组,消费者组则是一组中存在多个消费者,消费者消费Broker中当前Topic的不同分区中的消息,消费者组之间互不影响,所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。某一个分区中的消息只能够一个消费者组中的一个消费者所消费
- Broker:经纪人,一台Kafka服务器就是一个Broker,一个集群由多个Broker组成,一个Broker可以容纳多个Topic。
- Topic:主题,可以理解为一个队列,生产者和消费者都是面向一个Topic
- Partition:分区,为了实现扩展性,一个非常大的Topic可以分布到多个Broker上,一个Topic可以分为多个Partition,每个Partition是一个有序的队列(分区有序,不能保证全局有序)
- Replica:副本Replication,为保证集群中某个节点发生故障,节点上的Partition数据不丢失,Kafka可以正常的工作,Kafka提供了副本机制,一个Topic的每个分区有若干个副本,一个Leader和多个Follower
- Leader:每个分区多个副本的主角色,生产者发送数据的对象,以及消费者消费数据的对象都是Leader。
- Follower:每个分区多个副本的从角色,实时的从Leader中同步数据,保持和Leader数据的同步,Leader发生故障的时候,某个Follower会成为新的Leader。
上述一个Topic会产生多个分区Partition,分区中分为Leader和Follower,消息一般发送到Leader,Follower通过数据的同步与Leader保持同步,消费的话也是在Leader中发生消费,如果多个消费者,则分别消费Leader和各个Follower中的消息,当Leader发生故障的时候,某个Follower会成为主节点,此时会对齐消息的偏移量。
利用Docker容器快速构建Kafka环境
kafka需要zookeeper管理,所以需要先安装zookeeper
⚾️ 安装zookeeper和kafka
docker pull wurstmeister/zookeeper
docker pull wurstmeister/kafka
⚾️ 启动zookeeper
# 端口映射到本地的2181
docker run -d --name zookeeper -p 2181:2181 -t wurstmeister/zookeeper
⚾️ 启动kafka
# 本机IP使用ifconfig命令查看
docker run -d --name kafka \
-p 9092:9092 \
-e KAFKA_BROKER_ID=0 \
-e KAFKA_ZOOKEEPER_CONNECT=本机ip:2181 \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://本机ip:9092 \
-e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 wurstmeister/kafka
# 启动示例
docker run -d --name kafka \
-p 9092:9092 \
-e KAFKA_BROKER_ID=0 \
-e KAFKA_ZOOKEEPER_CONNECT=10.31.154.242:2181 \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://10.31.154.242:9092 \
-e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 wurstmeister/kafka
⚾️ 消息测试
🔧kafka自带了终端工具,可以在终端测试
# 进入容器
docker exec -it {container-id} bash
# 进入kafka安装目录
cd cd opt/kafka_2.13-2.8.1/bin/
# 创建一个叫test的topic
./kafka-console-producer.sh --broker-list localhost:9092 --topic "test"
# 输入一个消息后确认,然后运行consumer查看是否有消息
kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic "test" --from-beginning
# 终端输出刚才输入的内容即为构建成功
在Python中使用kafka
参考文档:Kafka-Python Api
⚾️ 构建producer
每一秒向Topic发一条消息
import json
import time
from datetime import datetime
from kafka import KafkaConsumer, KafkaAdminClient, KafkaProducer
from kafka.admin import NewTopic
from kafka.errors import KafkaError
class KafkaApi:
""" kafka utils
SERVER = '127.0.0.1:9092' TOPIC = 'test'
"""
def __init__(self, broker=""):
self.brokers = broker
self.producer = KafkaProducer(
bootstrap_servers=self.brokers,
key_serializer=lambda item: json.dumps(item).encode(),
value_serializer=lambda item: json.dumps(item).encode()
)
def create_topic(self, topic, num_partitions=1, replication_factor=1):
"""创建topic"""
if self.is_exists(topic):
raise Exception("topic 已存在")
client = KafkaAdminClient(bootstrap_servers=self.brokers)
topic_list = [NewTopic(
name=topic,
num_partitions=num_partitions,
replication_factor=replication_factor
)]
client.create_topics(new_topics=topic_list)
def is_exists(self, topic):
topics = self.get_all_topics()
return topic in topics
def get_all_topics(self):
"""获取所有topic"""
consumer = KafkaConsumer(bootstrap_servers=self.brokers)
return consumer.topics()
def send(self, topic, value, key=None):
if value is None:
raise 'content 不能为空'
try:
if key:
self.producer.send(topic, value=value, key=key)
else:
self.producer.send(topic, value=value)
except KafkaError as _:
self.producer.close()
class KafkaService:
"""
外发信息服务
"""
def __init__(self):
self.api = KafkaApi()
self.topics = []
def send_kafka(self, topic, content, key=None):
if content is None:
raise '发送内容不能为空'
# 判断topic是否存在,不存在则创建一个
if topic not in self.topics:
if not self.api.is_exists(topic):
self.api.create_topic(topic)
self.topics.append(topic)
try:
self.api.send(topic, content, key)
print("入kafka成功")
except KafkaError as error:
# 如果遇到发送失败,则再重试3次
retry = 0
while retry < 3:
# 暂停0.1秒
time.sleep(0.1)
try:
self.api.send(topic, content)
except KafkaError as _:
retry += 1
continue
break
if __name__ == '__main__':
s = KafkaService()
while 1:
data = {'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), "msg": "成功"}
s.send_kafka("ichpan", "下午好, 安同学!")
time.sleep(0.2)
⚾️ 构建consumer
# consumer.py
import json
from kafka import KafkaConsumer
# 第一个参数为topic的名称
# bootstrap_servers: 指定kafka服务器
# group_id : 指定此消费者实例属于的组名,可以不指定
# auto.offset.reset关乎kafka数据的读取,是一个非常重要的设置。常用的二个值是latest和earliest,默认是latest。
# - earliest 当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
# - latest 当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据
consumer = KafkaConsumer(
"ichpan",
bootstrap_servers='',
)
for msg in consumer:
print(msg.key, json.loads(msg.value), dir(msg))
⚾️ 效果展示
总结
这就是一个完整运行Kafka消息队列的全过程,原理和redis发布者订阅者模式差不多,可以理解就是基于Channel
的,在使用中我们可以查看容器的log来排除错误信息,也是非常方便的。同时也封装了kafka如何创建topic等操作,还是要对照文档来研发。